1
0
Fork 0
mirror of https://github.com/xHyroM/lighteco.git synced 2024-11-10 01:18:07 +01:00

feat: pool settings

This commit is contained in:
Jozef Steinhübl 2023-08-29 13:11:16 +02:00
parent c52e9da63d
commit 2cfba60fbe
30 changed files with 424 additions and 139 deletions

View file

@ -16,10 +16,33 @@ public interface LightEco {
*/ */
@NonNull Platform getPlatform(); @NonNull Platform getPlatform();
/**
* Gets the {@link UserManager}, which manages the users.
*
* @return the user manager
*/
@NonNull UserManager getUserManager(); @NonNull UserManager getUserManager();
/**
* Gets the {@link CurrencyManager}, which manages the currencies.
*
* @return the currency manager
*/
@NonNull CurrencyManager getCurrencyManager(); @NonNull CurrencyManager getCurrencyManager();
/**
* Gets the {@link CommandManager}, which manages the commands.
*
* @return the command manager
*/
@NonNull CommandManager getCommandManager(); @NonNull CommandManager getCommandManager();
/**
* Gets the {@link PlayerAdapter} for a player class.
*
* @param playerClass the player class
* @return the player adapter
* @param <T> the player class
*/
<T> @NonNull PlayerAdapter<T> getPlayerAdapter(@NonNull Class<T> playerClass); <T> @NonNull PlayerAdapter<T> getPlayerAdapter(@NonNull Class<T> playerClass);
} }

View file

@ -13,7 +13,7 @@ public final class LightEcoProvider {
LightEco instance = LightEcoProvider.instance; LightEco instance = LightEcoProvider.instance;
if (instance == null) { if (instance == null) {
throw new NotLoadedException(); throw new NotLoadedException();
}; }
return instance; return instance;
} }
@ -22,7 +22,7 @@ public final class LightEcoProvider {
public static void set(LightEco instance) { public static void set(LightEco instance) {
if (LightEcoProvider.instance != null) { if (LightEcoProvider.instance != null) {
throw new IllegalStateException("LightEco is already loaded!"); throw new IllegalStateException("LightEco is already loaded!");
}; }
LightEcoProvider.instance = instance; LightEcoProvider.instance = instance;
} }

View file

@ -5,6 +5,23 @@ import org.checkerframework.checker.nullness.qual.NonNull;
public interface CommandManager { public interface CommandManager {
// Make more transparent and freedom way to do this (more abstract) // Make more transparent and freedom way to do this (more abstract)
/**
* Registers commands for a currency.
*
* @param currency the currency to register the command for
*/
void registerCurrencyCommand(@NonNull Currency currency); void registerCurrencyCommand(@NonNull Currency currency);
/**
* Registers commands for a currency.
* <p>
* If main is true, the pay and balance commands will be exposed as main commands.
* You can also use {@link #registerCurrencyCommand(Currency)} to register the commands as subcommands.
* </p>
*
* @param currency the currency to register the command for
* @param main if pay and balance should be exposed as main commands
*/
void registerCurrencyCommand(@NonNull Currency currency, boolean main); void registerCurrencyCommand(@NonNull Currency currency, boolean main);
} }

View file

@ -5,6 +5,18 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID; import java.util.UUID;
public interface ContextManager<T> { public interface ContextManager<T> {
/**
* Gets the unique id of a player.
*
* @param context the context to get the unique id of
* @return the unique id of the player
*/
@NonNull UUID getPlayerUniqueId(@NonNull T context); @NonNull UUID getPlayerUniqueId(@NonNull T context);
/**
* Gets the player class of the platform.
*
* @return the player class
*/
@NonNull Class<?> getPlayerClass(); @NonNull Class<?> getPlayerClass();
} }

View file

@ -3,16 +3,42 @@ package dev.xhyrom.lighteco.api.manager;
import dev.xhyrom.lighteco.api.model.currency.Currency; import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface CurrencyManager { public interface CurrencyManager {
/**
* Gets all registered currencies.
*
* @return a collection of all registered currencies
*/
@NonNull Collection<Currency> getRegisteredCurrencies(); @NonNull Collection<Currency> getRegisteredCurrencies();
Currency getCurrency(@NonNull String identifier); /**
* Gets a currency by its identifier.
*
* @param identifier the identifier of the currency
* @return the currency, or null if not found
*/
@Nullable Currency getCurrency(@NonNull String identifier);
/**
* Registers a currency.
*
* @param currency the currency to register
*/
void registerCurrency(@NonNull Currency currency); void registerCurrency(@NonNull Currency currency);
List<User> getTopUsers(@NonNull Currency currency, int length); /**
* Gets the top users for a currency.
*
* @implNote This method is not cached. It fetches the data from the database every time it is called.
* @param currency the currency to get the top users for
* @param length the length of the list
* @return a future that completes with the top users (sorted from highest to lowest balance)
*/
CompletableFuture<List<User>> getTopUsers(@NonNull Currency currency, int length);
} }

View file

@ -13,6 +13,20 @@ public interface UserManager {
@NonNull CompletableFuture<Void> saveUser(@NonNull User user); @NonNull CompletableFuture<Void> saveUser(@NonNull User user);
/**
* Saves multiple users at once.
* <p>
* Uses TRANSACTION clause under the hood.
* If one of the users fails to save, the entire transaction will be rolled back.
* This is to ensure data integrity.
* If you want to save users individually, use {@link #saveUser(User)} instead.
* </p>
*
* @param users the users to save
* @return a future that completes when the users have been saved
*/
@NonNull CompletableFuture<Void> saveUsers(@NonNull User... users);
@Nullable User getUser(@NonNull UUID uniqueId); @Nullable User getUser(@NonNull UUID uniqueId);
boolean isLoaded(@NonNull UUID uniqueId); boolean isLoaded(@NonNull UUID uniqueId);

View file

@ -3,32 +3,57 @@ package dev.xhyrom.lighteco.api.model.currency;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.NumberFormat;
public interface Currency { public interface Currency {
String getIdentifier();
/** /**
* Get the type of the currency, either {@link Type#LOCAL} or {@link Type#GLOBAL} * Returns the identifier of the currency.
*
* @return the identifier
*/
String getIdentifier();
/**
* Returns the type of the currency, either {@link Type#LOCAL} or {@link Type#GLOBAL}
* *
* @see Type * @see Type
* @return The type of the currency * @return The type of the currency
*/ */
Type getType(); Type getType();
/**
* Format the given amount to a string
*
* @param amount The amount to format
* @return The formatted amount
*/
default String format(BigDecimal amount) {
NumberFormat format = NumberFormat.getInstance();
format.setMaximumFractionDigits(fractionalDigits());
return format.format(amount);
}
/**
* Determine if this currency is payable
*
* @return `true` if this currency is payable, `false` otherwise
*/
boolean isPayable(); boolean isPayable();
/** /**
* Get the number of fractional digits this currency has * Returns the number of fractional digits this currency has
* *
* @return The number of fractional digits * @return The number of fractional digits
*/ */
default int fractionalDigits() { default int fractionalDigits() {
return 0; return 0;
}; }
/** /**
* Get the users that have a balance in this currency * Returns the default balance for this currency
* *
* @return The users * @return The default balance
*/ */
BigDecimal getDefaultBalance(); BigDecimal getDefaultBalance();
@ -46,7 +71,7 @@ public interface Currency {
/** /**
* Represents the type of currency * Represents the type of currency
*/ */
public enum Type { enum Type {
/** /**
* A currency that is only available on a single server * A currency that is only available on a single server
*/ */

View file

@ -32,6 +32,10 @@ public interface User {
/** /**
* Set the balance of this user for the specified currency. * Set the balance of this user for the specified currency.
* <p>
* Save the user after setting the balance using {@link dev.xhyrom.lighteco.api.manager.UserManager#saveUser(User)}. <br/>
* If you're doing multiple changes, use {@link dev.xhyrom.lighteco.api.manager.UserManager#saveUsers(User...)}
* </p>
* *
* @param currency the currency * @param currency the currency
* @param balance the balance * @param balance the balance

View file

@ -3,6 +3,11 @@ package dev.xhyrom.lighteco.api.platform;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
public interface Platform { public interface Platform {
/**
* Get the type of this platform.
*
* @return
*/
@NonNull Type getType(); @NonNull Type getType();
enum Type { enum Type {

View file

@ -6,7 +6,19 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface PlayerAdapter<T> { public interface PlayerAdapter<T> {
/**
* Gets the user of a player.
*
* @param player the player to get the user of
* @return the user
*/
@NonNull User getUser(@NonNull T player); @NonNull User getUser(@NonNull T player);
/**
* Loads the user of a player.
*
* @param player the player to load the user of
* @return a future that completes with the user
*/
@NonNull CompletableFuture<User> loadUser(@NonNull T player); @NonNull CompletableFuture<User> loadUser(@NonNull T player);
} }

View file

@ -1,15 +1,31 @@
package dev.xhyrom.lighteco.api.storage; package dev.xhyrom.lighteco.api.storage;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface StorageProvider { public interface StorageProvider {
/**
* Initialize the storage provider.
*
* @throws Exception
*/
void init() throws Exception; void init() throws Exception;
/**
* Shutdown the storage provider.
*
* @throws Exception
*/
void shutdown() throws Exception; void shutdown() throws Exception;
@NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception; @NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception;
void saveUser(@NonNull User user) throws Exception; void saveUser(@NonNull User user) throws Exception;
void saveUsers(@NonNull User... users) throws Exception;
@NonNull List<User> getTopUsers(Currency currency, int length) throws Exception;
} }

View file

@ -18,7 +18,7 @@ public class TestCurrency2 implements Currency {
@Override @Override
public boolean isPayable() { public boolean isPayable() {
return false; return true;
} }
@Override @Override

View file

@ -3,9 +3,17 @@ package dev.xhyrom.lighteco.bukkittest;
import dev.xhyrom.lighteco.api.LightEco; import dev.xhyrom.lighteco.api.LightEco;
import dev.xhyrom.lighteco.api.LightEcoProvider; import dev.xhyrom.lighteco.api.LightEcoProvider;
import dev.xhyrom.lighteco.api.manager.CurrencyManager; import dev.xhyrom.lighteco.api.manager.CurrencyManager;
import dev.xhyrom.lighteco.api.model.user.User;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class TestPlugin extends JavaPlugin { import java.util.List;
import java.util.concurrent.CompletableFuture;
public class TestPlugin extends JavaPlugin implements Listener {
@Override @Override
public void onEnable() { public void onEnable() {
getLogger().info("TestPlugin loaded!"); getLogger().info("TestPlugin loaded!");
@ -15,6 +23,7 @@ public class TestPlugin extends JavaPlugin {
currencyManager.registerCurrency(new TestCurrency()); currencyManager.registerCurrency(new TestCurrency());
currencyManager.registerCurrency(new TestCurrency2()); currencyManager.registerCurrency(new TestCurrency2());
getServer().getPluginManager().registerEvents(this, this);
getLogger().info("TestCurrency registered!"); getLogger().info("TestCurrency registered!");
@ -23,4 +32,15 @@ public class TestPlugin extends JavaPlugin {
provider.getCommandManager().registerCurrencyCommand(currencyManager.getCurrency("test")); provider.getCommandManager().registerCurrencyCommand(currencyManager.getCurrency("test"));
provider.getCommandManager().registerCurrencyCommand(currencyManager.getCurrency("test2")); provider.getCommandManager().registerCurrencyCommand(currencyManager.getCurrency("test2"));
} }
@EventHandler
public void onChat(AsyncPlayerChatEvent event) {
LightEco provider = LightEcoProvider.get();
CurrencyManager currencyManager = provider.getCurrencyManager();
CompletableFuture<List<User>> topusers = currencyManager.getTopUsers(currencyManager.getCurrency("money"), 5);
for (User user : topusers.join()) {
event.getPlayer().sendMessage(user.getUniqueId() + " ("+ user.getUsername() +") " + ": " + user.getBalance(currencyManager.getCurrency("money")));
}
}
} }

View file

@ -8,7 +8,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.concurrent.CompletableFuture;
public class ApiCurrencyManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.currency.CurrencyManager> implements CurrencyManager { public class ApiCurrencyManager extends ApiAbstractManager<dev.xhyrom.lighteco.common.manager.currency.CurrencyManager> implements CurrencyManager {
public ApiCurrencyManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.currency.CurrencyManager handler) { public ApiCurrencyManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.currency.CurrencyManager handler) {
@ -38,10 +38,8 @@ public class ApiCurrencyManager extends ApiAbstractManager<dev.xhyrom.lighteco.c
} }
@Override @Override
public List<User> getTopUsers(@NonNull Currency currency, int length) { public CompletableFuture<List<User>> getTopUsers(@NonNull Currency currency, int length) {
dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getIfLoaded(currency.getIdentifier()); dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getIfLoaded(currency.getIdentifier());
return this.handler.getTopUsers(internal, length) return this.handler.getTopUsers(internal, length);
.stream().map(dev.xhyrom.lighteco.common.model.user.User::getProxy)
.collect(Collectors.toList());
} }
} }

View file

@ -5,6 +5,7 @@ import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -34,6 +35,11 @@ public class ApiUserManager extends ApiAbstractManager<dev.xhyrom.lighteco.commo
return this.plugin.getStorage().saveUser(user); return this.plugin.getStorage().saveUser(user);
} }
@Override
public @NonNull CompletableFuture<Void> saveUsers(@NotNull @NonNull User... users) {
return this.plugin.getStorage().saveUsers(users);
}
@Override @Override
public @Nullable User getUser(@NonNull UUID uniqueId) { public @Nullable User getUser(@NonNull UUID uniqueId) {
return wrap(this.handler.getIfLoaded(uniqueId)); return wrap(this.handler.getIfLoaded(uniqueId));

View file

@ -12,13 +12,10 @@ public class CurrencyMessageConfig extends OkaeriConfig {
public String give = "<currency> <dark_gray>| <gray>Gave <gold><target> <gold><amount> <dark_gray>| <gold><balance>"; public String give = "<currency> <dark_gray>| <gray>Gave <gold><target> <gold><amount> <dark_gray>| <gold><balance>";
public String take = "<currency> <dark_gray>| <gray>Took <gold><amount> <yellow>from <gold><target>"; public String take = "<currency> <dark_gray>| <gray>Took <gold><amount> <yellow>from <gold><target>";
public String pay = "<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target> <dark_gray>(<gold><taxed_amount> <yellow>after tax)</gray>"; public String pay = "<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target>";
public String payWithTax = "<currency> <dark_gray>| <gray>Paid <gold><amount> <yellow>to <gold><target> <dark_gray>(<gold><taxed_amount> <yellow>after tax<dark_gray>)";
public String wait = "<red>Please wait a moment before using this command again."; public String wait = "<red>Please wait a moment before using this command again.";
public String notEnoughMoney = "<red>You don't have enough money!"; public String notEnoughMoney = "<red>You don't have enough money!";
public String cannotSetNegative = "<red>Cannot set negative money!";
public String cannotGiveNegative = "<red>Cannot give negative money!";
public String cannotTakeNegative = "<red>Cannot take negative money!";
public String cannotPayNegative = "<red>Cannot pay negative money!";
public String cannotPaySelf = "<red>You cannot pay yourself!"; public String cannotPaySelf = "<red>You cannot pay yourself!";
} }

View file

@ -13,4 +13,19 @@ public class StorageDataConfig extends OkaeriConfig {
@Comment("Credentials for connecting to the database.") @Comment("Credentials for connecting to the database.")
public String username = "root"; public String username = "root";
public String password = "password"; public String password = "password";
@Comment("Maximum number of connections in the pool.")
public int maximumPoolSize = 10;
@Comment("Minimum idle connections in the pool.")
public int minimumIdle = 5;
@Comment("Maximum lifetime of a connection in the pool.")
public long maxLifetime = 1800000;
@Comment("Keep alive interval in milliseconds.")
public long keepAliveTime = 0;
@Comment("Connection timeout in milliseconds.")
public long connectionTimeout = 5000;
} }

View file

@ -0,0 +1,39 @@
package dev.xhyrom.lighteco.common.manager;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public abstract class ConcurrentManager<I, T> implements Manager<I, T> {
public final Map<I, T> map = new ConcurrentHashMap<>();
@Override
public Collection<I> keys() {
return this.map.keySet();
}
@Override
public Collection<T> values() {
return this.map.values();
}
@Override
public T getOrMake(I identifier) {
return this.map.computeIfAbsent(identifier, this::apply);
}
@Override
public T getIfLoaded(I identifier) {
return this.map.get(identifier);
}
@Override
public boolean isLoaded(I identifier) {
return this.map.containsKey(identifier);
}
@Override
public void unload(I identifier) {
this.map.remove(identifier);
}
}

View file

@ -15,6 +15,4 @@ public interface Manager<I, T> {
boolean isLoaded(I identifier); boolean isLoaded(I identifier);
void unload(I identifier); void unload(I identifier);
void unload(Collection<I> identifiers);
} }

View file

@ -4,7 +4,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public abstract class AbstractManager<I, T> implements Manager<I, T> { public abstract class SingleManager<I, T> implements Manager<I, T> {
public final Map<I, T> map = new HashMap<>(); public final Map<I, T> map = new HashMap<>();
@Override @Override
@ -36,9 +36,4 @@ public abstract class AbstractManager<I, T> implements Manager<I, T> {
public void unload(I identifier) { public void unload(I identifier) {
this.map.remove(identifier); this.map.remove(identifier);
} }
@Override
public void unload(Collection<I> identifiers) {
identifiers.forEach(this::unload);
}
} }

View file

@ -13,7 +13,6 @@ import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractCommandManager implements CommandManager { public abstract class AbstractCommandManager implements CommandManager {
public final LightEcoPlugin plugin; public final LightEcoPlugin plugin;
@ -102,16 +101,7 @@ public abstract class AbstractCommandManager implements CommandManager {
addToMustWait(sender.getUniqueId(), target.getUniqueId()); addToMustWait(sender.getUniqueId(), target.getUniqueId());
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN); amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
try { target.setBalance(currency, amount);
target.setBalance(currency, amount);
} catch (IllegalArgumentException e) {
sender.sendMessage(
miniMessage.deserialize(this.getConfig(currency).cannotSetNegative)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
return;
}
sender.sendMessage( sender.sendMessage(
miniMessage.deserialize( miniMessage.deserialize(
@ -131,16 +121,7 @@ public abstract class AbstractCommandManager implements CommandManager {
addToMustWait(sender.getUniqueId(), target.getUniqueId()); addToMustWait(sender.getUniqueId(), target.getUniqueId());
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN); amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
try { target.deposit(currency, amount);
target.deposit(currency, amount);
} catch (IllegalArgumentException e) {
sender.sendMessage(
miniMessage.deserialize(this.getConfig(currency).cannotGiveNegative)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
return;
}
sender.sendMessage( sender.sendMessage(
miniMessage.deserialize( miniMessage.deserialize(
@ -161,16 +142,7 @@ public abstract class AbstractCommandManager implements CommandManager {
addToMustWait(sender.getUniqueId(), target.getUniqueId()); addToMustWait(sender.getUniqueId(), target.getUniqueId());
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN); amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
try { target.withdraw(currency, amount);
target.withdraw(currency, amount);
} catch (IllegalArgumentException e) {
sender.sendMessage(
miniMessage.deserialize(this.getConfig(currency).cannotTakeNegative)
);
removeFromMustWait(target.getUniqueId(), sender.getUniqueId());
return;
}
sender.sendMessage( sender.sendMessage(
miniMessage.deserialize( miniMessage.deserialize(
@ -196,14 +168,6 @@ public abstract class AbstractCommandManager implements CommandManager {
return; return;
} }
if (amount.compareTo(BigDecimal.ZERO) < 0) {
sender.sendMessage(
miniMessage.deserialize(this.getConfig(currency).cannotPayNegative)
);
return;
}
amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN); amount = amount.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
User user = this.plugin.getUserManager().getIfLoaded(sender.getUniqueId()); User user = this.plugin.getUserManager().getIfLoaded(sender.getUniqueId());
@ -220,6 +184,7 @@ public abstract class AbstractCommandManager implements CommandManager {
// calculate tax using Currency#calculateTax // calculate tax using Currency#calculateTax
BigDecimal tax = currency.getProxy().calculateTax(user.getProxy(), amount); BigDecimal tax = currency.getProxy().calculateTax(user.getProxy(), amount);
tax = tax.setScale(currency.getProxy().fractionalDigits(), RoundingMode.DOWN);
// subtract tax from amount // subtract tax from amount
BigDecimal taxedAmount = amount.subtract(tax); BigDecimal taxedAmount = amount.subtract(tax);
@ -227,9 +192,14 @@ public abstract class AbstractCommandManager implements CommandManager {
target.deposit(currency, taxedAmount); target.deposit(currency, taxedAmount);
user.withdraw(currency, amount); user.withdraw(currency, amount);
String template = tax.compareTo(BigDecimal.ZERO) > 0
? this.getConfig(currency).payWithTax
: this.getConfig(currency).pay;
sender.sendMessage( sender.sendMessage(
miniMessage.deserialize( miniMessage.deserialize(
this.getConfig(currency).pay, template,
Placeholder.parsed("currency", currency.getIdentifier()), Placeholder.parsed("currency", currency.getIdentifier()),
Placeholder.parsed("target", target.getUsername()), Placeholder.parsed("target", target.getUsername()),
Placeholder.parsed("amount", amount.toPlainString()), Placeholder.parsed("amount", amount.toPlainString()),
@ -239,9 +209,7 @@ public abstract class AbstractCommandManager implements CommandManager {
) )
); );
CompletableFuture.allOf( this.plugin.getUserManager().saveUsers(user, target)
this.plugin.getUserManager().saveUser(user), .thenAccept(v -> removeFromMustWait(sender.getUniqueId(), target.getUniqueId()));
this.plugin.getUserManager().saveUser(target)
).thenAccept(v -> removeFromMustWait(sender.getUniqueId(), target.getUniqueId()));
} }
} }

View file

@ -1,17 +1,18 @@
package dev.xhyrom.lighteco.common.manager.currency; package dev.xhyrom.lighteco.common.manager.currency;
import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.common.manager.Manager; import dev.xhyrom.lighteco.common.manager.Manager;
import dev.xhyrom.lighteco.common.model.currency.Currency; import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface CurrencyManager extends Manager<String, Currency> { public interface CurrencyManager extends Manager<String, Currency> {
@NonNull Collection<Currency> getRegisteredCurrencies(); @NonNull Collection<Currency> getRegisteredCurrencies();
void registerCurrency(@NonNull Currency currency); void registerCurrency(@NonNull Currency currency);
List<User> getTopUsers(@NonNull Currency currency, int length); CompletableFuture<List<User>> getTopUsers(@NonNull Currency currency, int length);
} }

View file

@ -1,15 +1,16 @@
package dev.xhyrom.lighteco.common.manager.currency; package dev.xhyrom.lighteco.common.manager.currency;
import dev.xhyrom.lighteco.common.manager.AbstractManager; import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.common.manager.SingleManager;
import dev.xhyrom.lighteco.common.model.currency.Currency; import dev.xhyrom.lighteco.common.model.currency.Currency;
import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public class StandardCurrencyManager extends AbstractManager<String, Currency> implements CurrencyManager { public class StandardCurrencyManager extends SingleManager<String, Currency> implements CurrencyManager {
private final LightEcoPlugin plugin; private final LightEcoPlugin plugin;
public StandardCurrencyManager(LightEcoPlugin plugin) { public StandardCurrencyManager(LightEcoPlugin plugin) {
@ -18,7 +19,7 @@ public class StandardCurrencyManager extends AbstractManager<String, Currency> i
@Override @Override
public Currency apply(String identifier) { public Currency apply(String identifier) {
return null; throw new UnsupportedOperationException();
} }
@Override @Override
@ -39,9 +40,8 @@ public class StandardCurrencyManager extends AbstractManager<String, Currency> i
this.map.put(currency.getIdentifier(), currency); this.map.put(currency.getIdentifier(), currency);
} }
// TODO: finish
@Override @Override
public List<User> getTopUsers(@NonNull Currency currency, int length) { public CompletableFuture<List<User>> getTopUsers(@NonNull Currency currency, int length) {
return null; return this.plugin.getStorage().getTopUsers(currency.getProxy(), length);
} }
} }

View file

@ -1,15 +1,14 @@
package dev.xhyrom.lighteco.common.manager.user; package dev.xhyrom.lighteco.common.manager.user;
import dev.xhyrom.lighteco.common.manager.AbstractManager; import dev.xhyrom.lighteco.common.manager.ConcurrentManager;
import dev.xhyrom.lighteco.common.model.user.User; import dev.xhyrom.lighteco.common.model.user.User;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import java.util.HashSet; import java.util.Arrays;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class StandardUserManager extends AbstractManager<UUID, User> implements UserManager { public class StandardUserManager extends ConcurrentManager<UUID, User> implements UserManager {
private final LightEcoPlugin plugin; private final LightEcoPlugin plugin;
public StandardUserManager(LightEcoPlugin plugin) { public StandardUserManager(LightEcoPlugin plugin) {
@ -31,23 +30,17 @@ public class StandardUserManager extends AbstractManager<UUID, User> implements
return this.plugin.getStorage().loadUser(uniqueId, username); return this.plugin.getStorage().loadUser(uniqueId, username);
} }
@Override
public CompletableFuture<Void> load() {
Set<UUID> uniqueIds = new HashSet<>(keys());
uniqueIds.addAll(this.plugin.getBootstrap().getOnlinePlayers());
return uniqueIds.stream()
.map(id -> this.plugin.getStorage().loadUser(id))
.collect(CompletableFuture::allOf, (future, userFuture) -> future.join(), (future, userFuture) -> future.join());
}
@Override @Override
public CompletableFuture<Void> saveUser(User user) { public CompletableFuture<Void> saveUser(User user) {
return this.plugin.getStorage().saveUser(user.getProxy()); return this.plugin.getStorage().saveUser(user.getProxy());
} }
@Override @Override
public void invalidateCaches() { public CompletableFuture<Void> saveUsers(User... users) {
values().forEach(User::invalidateCaches); return this.plugin.getStorage().saveUsers(
Arrays.stream(users)
.map(User::getProxy)
.toArray(dev.xhyrom.lighteco.api.model.user.User[]::new)
);
} }
} }

View file

@ -8,11 +8,8 @@ import java.util.concurrent.CompletableFuture;
public interface UserManager extends Manager<UUID, User> { public interface UserManager extends Manager<UUID, User> {
CompletableFuture<Void> saveUser(User user); CompletableFuture<Void> saveUser(User user);
CompletableFuture<Void> saveUsers(User... users);
CompletableFuture<Void> load();
CompletableFuture<User> loadUser(UUID uniqueId); CompletableFuture<User> loadUser(UUID uniqueId);
CompletableFuture<User> loadUser(UUID uniqueId, String username); CompletableFuture<User> loadUser(UUID uniqueId, String username);
void invalidateCaches();
} }

View file

@ -5,6 +5,8 @@ import dev.xhyrom.lighteco.api.storage.StorageProvider;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import dev.xhyrom.lighteco.common.util.ThrowableRunnable; import dev.xhyrom.lighteco.common.util.ThrowableRunnable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -80,4 +82,14 @@ public class Storage {
public CompletableFuture<Void> saveUser(dev.xhyrom.lighteco.api.model.user.User user) { public CompletableFuture<Void> saveUser(dev.xhyrom.lighteco.api.model.user.User user) {
return future(() -> this.provider.saveUser(user)); return future(() -> this.provider.saveUser(user));
} }
public CompletableFuture<Void> saveUsers(dev.xhyrom.lighteco.api.model.user.User... users) {
return future(() -> this.provider.saveUsers(users));
}
// Return ApiUser instead of User
// We don't do anything with this
public CompletableFuture<List<dev.xhyrom.lighteco.api.model.user.User>> getTopUsers(dev.xhyrom.lighteco.api.model.currency.Currency currency, int length) {
return future(() -> this.provider.getTopUsers(currency, length));
}
} }

View file

@ -1,12 +1,17 @@
package dev.xhyrom.lighteco.common.storage.provider.memory; package dev.xhyrom.lighteco.common.storage.provider.memory;
import dev.xhyrom.lighteco.api.model.currency.Currency;
import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.model.user.User;
import dev.xhyrom.lighteco.api.storage.StorageProvider; import dev.xhyrom.lighteco.api.storage.StorageProvider;
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class MemoryStorageProvider implements StorageProvider { public class MemoryStorageProvider implements StorageProvider {
@ -41,6 +46,23 @@ public class MemoryStorageProvider implements StorageProvider {
this.userDatabase.put(user.getUniqueId(), user); this.userDatabase.put(user.getUniqueId(), user);
} }
@Override
public void saveUsers(@NotNull @NonNull User... users) {
for (User user : users) {
this.userDatabase.put(user.getUniqueId(), user);
}
}
@Override
public @NonNull List<User> getTopUsers(Currency currency, int length) throws Exception {
return userDatabase.values().stream().sorted((user1, user2) -> {
BigDecimal balance1 = user1.getBalance(currency);
BigDecimal balance2 = user2.getBalance(currency);
return balance1.compareTo(balance2);
}).limit(length).toList();
}
private User createUser(UUID uniqueId, String username, User data) { private User createUser(UUID uniqueId, String username, User data) {
dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId); dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId);
if (username != null) if (username != null)

View file

@ -19,7 +19,7 @@ public enum SqlStatements {
"SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ?1 UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ?1 ) AS combined_currencies;", "SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ?1 UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ?1 ) AS combined_currencies;",
"SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ?1 UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ?1 ) AS combined_currencies;", "SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ?1 UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ?1 ) AS combined_currencies;",
"SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ? UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ? ) AS combined_currencies;", "SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ? UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ? ) AS combined_currencies;",
null null // same as mariadb
); );
public final String sqlite; public final String sqlite;

View file

@ -8,11 +8,13 @@ import dev.xhyrom.lighteco.common.storage.StorageType;
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory; import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.*; import java.sql.*;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
@ -21,6 +23,8 @@ public class SqlStorageProvider implements StorageProvider {
private static String SAVE_USER_LOCAL_CURRENCY; private static String SAVE_USER_LOCAL_CURRENCY;
private static String SAVE_USER_GLOBAL_CURRENCY; private static String SAVE_USER_GLOBAL_CURRENCY;
private static String LOAD_WHOLE_USER; private static String LOAD_WHOLE_USER;
private static final String GET_TOP_X_USERS_LOCAL = "SELECT uuid, balance FROM {prefix}_{context}_users WHERE currency_identifier = ? ORDER BY balance DESC LIMIT ?;";
private static final String GET_TOP_X_USERS_GLOBAL = "SELECT uuid, balance FROM {prefix}_users WHERE currency_identifier = ? ORDER BY balance DESC LIMIT ?;";
private final LightEcoPlugin plugin; private final LightEcoPlugin plugin;
private final ConnectionFactory connectionFactory; private final ConnectionFactory connectionFactory;
@ -109,37 +113,103 @@ public class SqlStorageProvider implements StorageProvider {
try (PreparedStatement psGlobal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY)); try (PreparedStatement psGlobal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY));
PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY))) { PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY))) {
for (Currency currency : this.plugin.getCurrencyManager().getRegisteredCurrencies()) { saveBalances(psGlobal, psLocal, user, uniqueIdString);
BigDecimal balance = user.getBalance(currency.getProxy());
if (balance.compareTo(BigDecimal.ZERO) == 0) continue;
switch (currency.getType()) {
case GLOBAL -> {
psGlobal.setString(1, uniqueIdString);
psGlobal.setString(2, currency.getIdentifier());
psGlobal.setBigDecimal(3, balance);
if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName()))
psGlobal.setBigDecimal(4, balance);
psGlobal.addBatch();
}
case LOCAL -> {
psLocal.setString(1, uniqueIdString);
psLocal.setString(2, currency.getIdentifier());
psLocal.setBigDecimal(3, balance);
if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName()))
psLocal.setBigDecimal(4, balance);
psLocal.addBatch();
}
}
}
psGlobal.executeBatch(); psGlobal.executeBatch();
psLocal.executeBatch(); psLocal.executeBatch();
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Failed to save user " + user.getUniqueId(), e); throw new SQLException("Failed to save user " + user.getUniqueId(), e);
}
}
}
@Override
public void saveUsers(@NotNull @NonNull User... users) throws Exception {
// use transaction
try (Connection c = this.connectionFactory.getConnection()) {
try {
c.setAutoCommit(false);
try (PreparedStatement psGlobal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY));
PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY))) {
for (User user : users) {
String uniqueIdString = user.getUniqueId().toString();
saveBalances(psGlobal, psLocal, user, uniqueIdString);
}
psGlobal.executeBatch();
psLocal.executeBatch();
} catch (SQLException e) {
throw new SQLException("Failed to save users", e);
}
c.commit();
} catch (SQLException e) {
c.rollback();
throw e;
}
}
}
@Override
public @NonNull List<User> getTopUsers(dev.xhyrom.lighteco.api.model.currency.Currency apiCurrency, int length) throws Exception {
Currency currency = this.plugin.getCurrencyManager().getIfLoaded(apiCurrency.getIdentifier());
String statement = currency.getType() == dev.xhyrom.lighteco.api.model.currency.Currency.Type.GLOBAL
? GET_TOP_X_USERS_GLOBAL
: GET_TOP_X_USERS_LOCAL;
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(statement))) {
ps.setString(1, currency.getIdentifier());
ps.setInt(2, length);
ResultSet rs = ps.executeQuery();
List<User> users = new ArrayList<>();
while (rs.next()) {
String uniqueIdString = rs.getString("uuid");
UUID uniqueId = UUID.fromString(uniqueIdString);
BigDecimal balance = rs.getBigDecimal("balance");
dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId);
user.setBalance(currency, balance);
users.add(user.getProxy());
}
return users;
}
}
}
private void saveBalances(PreparedStatement psGlobal, PreparedStatement psLocal, User user, String uniqueIdString) throws SQLException {
for (Currency currency : this.plugin.getCurrencyManager().getRegisteredCurrencies()) {
BigDecimal balance = user.getBalance(currency.getProxy());
if (balance.compareTo(BigDecimal.ZERO) == 0) continue;
switch (currency.getType()) {
case GLOBAL -> {
psGlobal.setString(1, uniqueIdString);
psGlobal.setString(2, currency.getIdentifier());
psGlobal.setBigDecimal(3, balance);
if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName()))
psGlobal.setBigDecimal(4, balance);
psGlobal.addBatch();
}
case LOCAL -> {
psLocal.setString(1, uniqueIdString);
psLocal.setString(2, currency.getIdentifier());
psLocal.setBigDecimal(3, balance);
if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName()))
psLocal.setBigDecimal(4, balance);
psLocal.addBatch();
}
} }
} }
} }

View file

@ -64,12 +64,12 @@ public abstract class HikariConnectionFactory implements ConnectionFactory {
this.setProperties(config, properties); this.setProperties(config, properties);
// configure the connection pool // configure the connection pool
// config.setMaximumPoolSize(1); config.setMaximumPoolSize(this.configuration.maximumPoolSize);
// config.setMinimumIdle(10); config.setMinimumIdle(this.configuration.minimumIdle);
// config.setMaxLifetime(1800000); config.setMaxLifetime(this.configuration.maxLifetime);
// config.setKeepaliveTime(0); config.setKeepaliveTime(this.configuration.keepAliveTime);
// config.setConnectionTimeout(5000); config.setConnectionTimeout(this.configuration.connectionTimeout);
// config.setInitializationFailTimeout(-1); config.setInitializationFailTimeout(-1);
this.hikari = new HikariDataSource(config); this.hikari = new HikariDataSource(config);