99import fr .openmc .core .bootstrap .features .annotations .Credit ;
1010import fr .openmc .core .bootstrap .features .types .DatabaseFeature ;
1111import fr .openmc .core .bootstrap .features .types .HasCommands ;
12+ import fr .openmc .core .bootstrap .integration .OMCLogger ;
13+ import fr .openmc .core .OMCPlugin ;
1214import fr .openmc .core .features .economy .commands .Baltop ;
1315import fr .openmc .core .features .economy .commands .History ;
1416import fr .openmc .core .features .economy .commands .Money ;
1517import fr .openmc .core .features .economy .commands .Pay ;
1618import fr .openmc .core .features .economy .models .EconomyPlayer ;
1719import fr .openmc .core .hooks .itemsadder .ItemsAdderHook ;
1820import lombok .Getter ;
21+ import org .bukkit .Bukkit ;
22+ import org .bukkit .scheduler .BukkitTask ;
1923
2024import javax .annotation .Nullable ;
2125import java .math .BigDecimal ;
@@ -30,6 +34,10 @@ public class EconomyManager extends Feature implements DatabaseFeature, HasComma
3034 private static Map <UUID , EconomyPlayer > balances ;
3135
3236 private static Dao <EconomyPlayer , String > playersDao ;
37+ private static final Set <UUID > dirtyBalances = new HashSet <>();
38+ private static final Object balancesLock = new Object ();
39+ private static final long AUTO_SAVE_INTERVAL_TICKS = 20L * 60L * 5L ;
40+ private static BukkitTask autoSaveTask ;
3341
3442 private static final DecimalFormat decimalFormat = new DecimalFormat ("#.##" );
3543 private static final NavigableMap <Long , String > suffixes = new TreeMap <>(Map .of (
@@ -43,6 +51,8 @@ public class EconomyManager extends Feature implements DatabaseFeature, HasComma
4351 @ Override
4452 public void init () {
4553 balances = loadAllBalances ();
54+ dirtyBalances .clear ();
55+ startAutoSaveTask ();
4656 }
4757
4858 @ Override
@@ -61,18 +71,29 @@ public void initDB(ConnectionSource connectionSource) throws SQLException {
6171 playersDao = DaoManager .createDao (connectionSource , EconomyPlayer .class );
6272 }
6373
74+ @ Override
75+ protected void save () {
76+ stopAutoSaveTask ();
77+ saveAllBalances ();
78+ }
79+
6480 public static double getBalance (UUID playerUUID ) {
65- EconomyPlayer bank = getPlayerBank (playerUUID );
66- return bank .getBalance ();
81+ synchronized (balancesLock ) {
82+ EconomyPlayer bank = getPlayerBank (playerUUID );
83+ return bank .getBalance ();
84+ }
6785 }
6886
6987 public static void addBalance (UUID playerUUID , double amount ) {
7088 addBalance (playerUUID , amount , null );
7189 }
7290
7391 public static void addBalance (UUID playerUUID , double amount , @ Nullable String reason ) {
74- EconomyPlayer bank = getPlayerBank (playerUUID );
75- bank .deposit (amount );
92+ synchronized (balancesLock ) {
93+ EconomyPlayer bank = getPlayerBank (playerUUID );
94+ bank .deposit (amount );
95+ savePlayerBank (bank );
96+ }
7697
7798 if (reason != null ) {
7899 TransactionsManager .registerTransaction (new Transaction (
@@ -83,32 +104,33 @@ public static void addBalance(UUID playerUUID, double amount, @Nullable String r
83104 ));
84105 }
85106
86- savePlayerBank (bank );
87107 }
88108
89109 public static boolean withdrawBalance (UUID playerUUID , double amount ) {
90110 return withdrawBalance (playerUUID , amount , null );
91111 }
92112
93113 public static boolean withdrawBalance (UUID playerUUID , double amount , @ Nullable String reason ) {
94- EconomyPlayer bank = getPlayerBank (playerUUID );
114+ synchronized (balancesLock ) {
115+ EconomyPlayer bank = getPlayerBank (playerUUID );
95116
96- if (bank .withdraw (amount )) {
97- if (reason != null ) {
98- TransactionsManager .registerTransaction (new Transaction (
99- "CONSOLE" ,
100- playerUUID .toString (),
101- amount ,
102- reason
103- ));
117+ if (!bank .withdraw (amount )) {
118+ return false ;
104119 }
105120
106121 savePlayerBank (bank );
122+ }
107123
108- return true ;
124+ if (reason != null ) {
125+ TransactionsManager .registerTransaction (new Transaction (
126+ "CONSOLE" ,
127+ playerUUID .toString (),
128+ amount ,
129+ reason
130+ ));
109131 }
110132
111- return false ;
133+ return true ;
112134 }
113135
114136 /**
@@ -152,10 +174,11 @@ public static boolean transferBalance(UUID fromPlayer, UUID toPlayer, double amo
152174 }
153175
154176 public static void setBalance (UUID playerUUID , double amount ) {
155- EconomyPlayer bank = getPlayerBank (playerUUID );
156- bank .withdraw (bank .getBalance ());
157- bank .deposit (amount );
158- savePlayerBank (bank );
177+ synchronized (balancesLock ) {
178+ EconomyPlayer bank = getPlayerBank (playerUUID );
179+ bank .setBalance (amount );
180+ savePlayerBank (bank );
181+ }
159182 }
160183
161184 public static String getMiniBalance (UUID playerUUID ) {
@@ -165,19 +188,51 @@ public static String getMiniBalance(UUID playerUUID) {
165188 }
166189
167190 public static void savePlayerBank (EconomyPlayer player ) {
168- try {
191+ synchronized ( balancesLock ) {
169192 balances .put (player .getPlayerUUID (), player );
170- playersDao .createOrUpdate (player );
171- } catch (SQLException e ) {
172- throw new RuntimeException (e );
193+ dirtyBalances .add (player .getPlayerUUID ());
173194 }
174195 }
175196
176197 public static EconomyPlayer getPlayerBank (UUID playerUUID ) {
177- EconomyPlayer bank = balances .get (playerUUID );
178- if (bank != null )
179- return bank ;
180- return new EconomyPlayer (playerUUID );
198+ synchronized (balancesLock ) {
199+ return balances .computeIfAbsent (playerUUID , EconomyPlayer ::new );
200+ }
201+ }
202+
203+ public static void saveAllBalances () {
204+ List <EconomyPlayer > playersToSave ;
205+
206+ synchronized (balancesLock ) {
207+ if (dirtyBalances .isEmpty ()) {
208+ return ;
209+ }
210+
211+ playersToSave = dirtyBalances .stream ()
212+ .map (balances ::get )
213+ .filter (Objects ::nonNull )
214+ .map (player -> new EconomyPlayer (player .getPlayerUUID (), player .getBalance ()))
215+ .toList ();
216+ dirtyBalances .clear ();
217+ }
218+
219+ try {
220+ playersDao .callBatchTasks (() -> {
221+ for (EconomyPlayer player : playersToSave ) {
222+ playersDao .createOrUpdate (player );
223+ }
224+
225+ return null ;
226+ });
227+ } catch (Exception e ) {
228+ synchronized (balancesLock ) {
229+ for (EconomyPlayer player : playersToSave ) {
230+ dirtyBalances .add (player .getPlayerUUID ());
231+ }
232+ }
233+
234+ OMCLogger .error ("Failed to save economy balances" , e );
235+ }
181236 }
182237
183238 public static Map <UUID , EconomyPlayer > loadAllBalances () {
@@ -194,6 +249,28 @@ public static Map<UUID, EconomyPlayer> loadAllBalances() {
194249 return balances ;
195250 }
196251
252+ private static void startAutoSaveTask () {
253+ if (OMCPlugin .isUnitTestVersion () || autoSaveTask != null ) {
254+ return ;
255+ }
256+
257+ autoSaveTask = Bukkit .getScheduler ().runTaskTimerAsynchronously (
258+ OMCPlugin .getInstance (),
259+ EconomyManager ::saveAllBalances ,
260+ AUTO_SAVE_INTERVAL_TICKS ,
261+ AUTO_SAVE_INTERVAL_TICKS
262+ );
263+ }
264+
265+ private static void stopAutoSaveTask () {
266+ if (autoSaveTask == null ) {
267+ return ;
268+ }
269+
270+ autoSaveTask .cancel ();
271+ autoSaveTask = null ;
272+ }
273+
197274 public static String getFormattedBalance (UUID playerUUID ) {
198275 String balance = String .valueOf (getBalance (playerUUID ));
199276 Currency currency = Currency .getInstance (Locale .FRANCE );
0 commit comments