Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 150 additions & 33 deletions src/main/java/fr/openmc/core/features/economy/EconomyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
import fr.openmc.core.bootstrap.features.annotations.Credit;
import fr.openmc.core.bootstrap.features.types.DatabaseFeature;
import fr.openmc.core.bootstrap.features.types.HasCommands;
import fr.openmc.core.bootstrap.integration.OMCLogger;
import fr.openmc.core.OMCPlugin;
import fr.openmc.core.features.economy.commands.Baltop;
import fr.openmc.core.features.economy.commands.History;
import fr.openmc.core.features.economy.commands.Money;
import fr.openmc.core.features.economy.commands.Pay;
import fr.openmc.core.features.economy.models.EconomyPlayer;
import fr.openmc.core.hooks.itemsadder.ItemsAdderHook;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;

import javax.annotation.Nullable;
import java.math.BigDecimal;
Expand All @@ -26,10 +29,14 @@

@Credit(developers = {"Axeno", "Piquel Chips", "PuppyTransGirl", "Gyro"})
public class EconomyManager extends Feature implements DatabaseFeature, HasCommands {
@Getter
private static Map<UUID, EconomyPlayer> balances;

private static Dao<EconomyPlayer, String> playersDao;
private static final Set<UUID> dirtyBalances = new HashSet<>();
private static final Object balancesLock = new Object();
private static final Object saveLock = new Object();
private static final long AUTO_SAVE_INTERVAL_TICKS = 20L * 60L * 5L; // 5 minutes
private static BukkitTask autoSaveTask;

private static final DecimalFormat decimalFormat = new DecimalFormat("#.##");
private static final NavigableMap<Long, String> suffixes = new TreeMap<>(Map.of(
Expand All @@ -43,6 +50,8 @@ public class EconomyManager extends Feature implements DatabaseFeature, HasComma
@Override
public void init() {
balances = loadAllBalances();
dirtyBalances.clear();
startAutoSaveTask();
}

@Override
Expand All @@ -61,18 +70,39 @@ public void initDB(ConnectionSource connectionSource) throws SQLException {
playersDao = DaoManager.createDao(connectionSource, EconomyPlayer.class);
}

@Override
protected void save() {
stopAutoSaveTask();
saveAllBalances(true);
}

public static double getBalance(UUID playerUUID) {
EconomyPlayer bank = getPlayerBank(playerUUID);
return bank.getBalance();
synchronized (balancesLock) {
EconomyPlayer bank = balances.get(playerUUID);
return bank == null ? 0 : bank.getBalance();
}
}

public static Map<UUID, EconomyPlayer> getBalances() {
synchronized (balancesLock) {
Map<UUID, EconomyPlayer> snapshot = new HashMap<>();

balances.forEach((playerUUID, player) -> snapshot.put(playerUUID, copyPlayer(player)));

return Collections.unmodifiableMap(snapshot);
}
}

public static void addBalance(UUID playerUUID, double amount) {
addBalance(playerUUID, amount, null);
}

public static void addBalance(UUID playerUUID, double amount, @Nullable String reason) {
EconomyPlayer bank = getPlayerBank(playerUUID);
bank.deposit(amount);
synchronized (balancesLock) {
EconomyPlayer bank = getOrCreatePlayerBank(playerUUID);
bank.deposit(amount);
markPlayerBankDirty(bank);
}

if (reason != null) {
TransactionsManager.registerTransaction(new Transaction(
Expand All @@ -83,32 +113,33 @@ public static void addBalance(UUID playerUUID, double amount, @Nullable String r
));
}

savePlayerBank(bank);
}

public static boolean withdrawBalance(UUID playerUUID, double amount) {
return withdrawBalance(playerUUID, amount, null);
}

public static boolean withdrawBalance(UUID playerUUID, double amount, @Nullable String reason) {
EconomyPlayer bank = getPlayerBank(playerUUID);
synchronized (balancesLock) {
EconomyPlayer bank = getOrCreatePlayerBank(playerUUID);

if (bank.withdraw(amount)) {
if (reason != null) {
TransactionsManager.registerTransaction(new Transaction(
"CONSOLE",
playerUUID.toString(),
amount,
reason
));
if (!bank.withdraw(amount)) {
return false;
}

savePlayerBank(bank);
markPlayerBankDirty(bank);
}

return true;
if (reason != null) {
TransactionsManager.registerTransaction(new Transaction(
"CONSOLE",
playerUUID.toString(),
amount,
reason
));
}

return false;
return true;
}

/**
Expand Down Expand Up @@ -152,10 +183,11 @@ public static boolean transferBalance(UUID fromPlayer, UUID toPlayer, double amo
}

public static void setBalance(UUID playerUUID, double amount) {
EconomyPlayer bank = getPlayerBank(playerUUID);
bank.withdraw(bank.getBalance());
bank.deposit(amount);
savePlayerBank(bank);
synchronized (balancesLock) {
EconomyPlayer bank = getOrCreatePlayerBank(playerUUID);
bank.setBalance(amount);
markPlayerBankDirty(bank);
}
}

public static String getMiniBalance(UUID playerUUID) {
Expand All @@ -164,20 +196,83 @@ public static String getMiniBalance(UUID playerUUID) {
return getFormattedSimplifiedNumber(balance);
}

public static void savePlayerBank(EconomyPlayer player) {
try {
balances.put(player.getPlayerUUID(), player);
playersDao.createOrUpdate(player);
} catch (SQLException e) {
throw new RuntimeException(e);
public static void markPlayerBankDirty(EconomyPlayer player) {
synchronized (balancesLock) {
balances.put(player.getPlayerUUID(), copyPlayer(player));
dirtyBalances.add(player.getPlayerUUID());
}
}

/**
* Returns a snapshot of a player's economy data.
* <p>
* Mutating the returned {@link EconomyPlayer} does not update the cache or mark
* the balance dirty. Use {@link #setBalance(UUID, double)},
* {@link #addBalance(UUID, double)} or {@link #withdrawBalance(UUID, double)}
* to change a player's balance.
*/
public static EconomyPlayer getPlayerBank(UUID playerUUID) {
EconomyPlayer bank = balances.get(playerUUID);
if (bank != null)
return bank;
return new EconomyPlayer(playerUUID);
synchronized (balancesLock) {
EconomyPlayer bank = balances.get(playerUUID);
return bank == null ? new EconomyPlayer(playerUUID) : copyPlayer(bank);
}
}

private static EconomyPlayer getOrCreatePlayerBank(UUID playerUUID) {
return balances.computeIfAbsent(playerUUID, EconomyPlayer::new);
}

public static void saveAllBalances() {
saveAllBalances(false);
}

private static void saveAllBalances(boolean finalSave) {
synchronized (saveLock) {
do {
List<EconomyPlayer> playersToSave;

synchronized (balancesLock) {
if (dirtyBalances.isEmpty()) {
return;
}

playersToSave = dirtyBalances.stream()
.map(balances::get)
.filter(Objects::nonNull)
.map(EconomyManager::copyPlayer)
.toList();
dirtyBalances.clear();
}

try {
playersDao.callBatchTasks(() -> {
for (EconomyPlayer player : playersToSave) {
playersDao.createOrUpdate(player);
}

return null;
});
} catch (Exception e) {
synchronized (balancesLock) {
for (EconomyPlayer player : playersToSave) {
dirtyBalances.add(player.getPlayerUUID());
}
}

if (finalSave) {
OMCLogger.error("CRITIQUE: Impossible de sauvegarder les soldes de l'economie pendant l'arret. Des soldes non sauvegardes peuvent etre perdus si le serveur s'arrete.", e);
} else {
OMCLogger.error("Impossible de sauvegarder les soldes de l'economie. Les soldes modifies seront reessayes a la prochaine sauvegarde.", e);
}

return;
}
} while (finalSave);
}
}

private static EconomyPlayer copyPlayer(EconomyPlayer player) {
return new EconomyPlayer(player.getPlayerUUID(), player.getBalance());
}

public static Map<UUID, EconomyPlayer> loadAllBalances() {
Expand All @@ -194,6 +289,28 @@ public static Map<UUID, EconomyPlayer> loadAllBalances() {
return balances;
}

private static void startAutoSaveTask() {
if (OMCPlugin.getInstance() == null || OMCPlugin.isUnitTestVersion() || autoSaveTask != null) {
return;
}

autoSaveTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
OMCPlugin.getInstance(),
() -> EconomyManager.saveAllBalances(),
AUTO_SAVE_INTERVAL_TICKS,
AUTO_SAVE_INTERVAL_TICKS
);
}

private static void stopAutoSaveTask() {
if (autoSaveTask == null) {
return;
}

autoSaveTask.cancel();
autoSaveTask = null;
}

public static String getFormattedBalance(UUID playerUUID) {
String balance = String.valueOf(getBalance(playerUUID));
Currency currency = Currency.getInstance(Locale.FRANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public EconomyPlayer(UUID playerUUID) {
this.balance = 0;
}

public EconomyPlayer(UUID playerUUID, double balance) {
this.playerUUID = playerUUID;
this.balance = balance;
}

public void deposit(double amount) {
balance += amount;
}
Expand All @@ -35,4 +40,8 @@ public boolean withdraw(double amount) {
}
return false;
}

public void setBalance(double balance) {
this.balance = balance;
}
}
Loading
Loading