-
Notifications
You must be signed in to change notification settings - Fork 15
PlayerStorage
PlayerStorage is a Fairy built-in automated in-memory storage load and save system, It's a very good system for you to store data that being collected from databases, So you don't need to take effort for it
It is design to be used with Repository Storage system, Currently there is only 1 implementations
As the name, Most of work related to IO will be done in off Main-Thread
- Player Data will automatically be loaded on Async Pre-Login, By blocking
Mojang Authentication Thread, we can ensure that Plugins doesn't know the Player, and still being Async - Player Data Save will be done in
Async Thread Pool, You can choose eitherSave on ModificationorSave on Quit, It's depend on how you want your system to works.
import org.bukkit.entity.Player;
import io.fairyproject.bean.Service;
import io.fairyproject.bukkit.player.storage.ThreadedPlayerStorage;
import io.fairyproject.bukkit.player.storage.ThreadedPlayerStorageConfiguration;
import io.fairyproject.task.Task;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* Player Storage requires being a Service
*/
@Service(name = "example:playerStorage")
public class MyPlayerStorage extends ThreadedPlayerStorage<MyData> {
/**
* Build Configuration, will be cached
*
* @return configuration
*/
@Override
protected ThreadedPlayerStorageConfiguration<MyData> buildStorageConfiguration() {
return new ThreadedPlayerStorageConfiguration<MyData>() {
@Override
public String getName() {
return "myData";
}
/**
* load async
* in this stage The Main Server doesn't yet register the Player, that means Bukkit.getPlayer() will not work in this stage
*
* @param uuid the UUID of the Player
* @param name the Name of the Player
* @return loaded data
*/
@Override
public MyData loadAsync(UUID uuid, String name) {
MyData myData = new MyData(uuid, name);
// do your load work
return myData;
}
@Override
public void saveAsync(UUID uuid, MyData myData) {
// do your save work
}
@Override
public boolean shouldUnloadOnQuit(Player player) {
// should unload on quit?
return true;
}
};
}
/**
* on Loaded in Mojang Authentication Thread
* in this stage The Main Server doesn't yet register the Player, that means Bukkit.getPlayer() will not work in this stage
*
* @param uuid the UUID of the Player
* @param name the Name of the Player
* @param myData the Data
*/
@Override
protected void onLoadedAsync(UUID uuid, String name, MyData myData) {
// For example, i want to calculate Player's Rank here since it's unnecessary to be done in main thread
myData.calculateRank();
}
/**
* in this stage Player were registered in the Server and it's in Main Thread
* You can do things safely
*
* @param player the Player
* @param myData the Data
*/
@Override
protected void onLoadedMain(Player player, MyData myData) {
// For example, i want to apply data that necessary to be done with registered player and in main thread
myData.apply(player);
}
/**
* Pre Data being unloaded from the Storage, it being designed so it will not unload until CompletableFuture finished
* If you have no work to done here you can just return CompletableFuture.completedFuture(myData);
* Please note that after onPreUnload finished, the work will NOT automatically port back to Async Thread
* So if the end of CompletableFuture was Main-Thread, please port back to Async Thread again so Data will be unload in async
*
* @param player the Player
* @param myData
* @return
*/
@Override
protected CompletableFuture<MyData> onPreUnload(Player player, MyData myData) {
/*
* For example, I have a future in my data that will be called sometimes after onPreUnload called
* I want to wait until the future completed and then remove Cache in my plugins inside of Main Thread
* Finally back to async thread and do Unload job
* I can totally achieve by using this
*/
return myData.getUnloadedFuture().thenApplyAsync(() -> {
myData.removeCache();
return myData;
}, Task.main())
// We port back to async here since it was back to main in previous chain
.thenAcceptAsync(ignored -> myData, Task.async());
}
}Or if you are using with Repository system, it's much easier, just change the buildConfiguration from above code to following codes
/**
* Build Configuration, will be cached
*
* @return configuration
*/
@Override
protected ThreadedPlayerStorageConfiguration<MyData> buildStorageConfiguration() {
Repository<MyData, UUID> repository = Storage.createRepository("myRepo", MyData.class);
return new ThreadedPlayerStorageConfigurationRepository<MyData>() {
/**
* Create data when it's not stored anywhere
*
* @param uuid the UUID of the Player
* @param name the Name of the Player
* @return the Data
*/
@Override
public MyData create(UUID uuid, String name) {
return new MyData(uuid, name);
}
/**
* get Repository, will NOT cached
*
* @return the Repository
*/
@Override
public Repository<MyData, UUID> getRepository() {
return repository;
}
@Override
public boolean shouldUnloadOnQuit(Player player) {
return true;
}
};
}Example for you to get or save data from the storage you created
Note that if you are using ThreadedPlayerStorage, the Data should always be loaded when you trying to get it, Unless you are doing something weird... more information check how ThreadedPlayerStorage were implemented
// Wiring the MyPlayerStorage to access it
@Autowired
private MyPlayerStorage myPlayerStorage;
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
final Player player = event.getEntity();
final Player killer = player.getKiller();
if (killer != null) {
// get Data from Storage
final MyData myData = this.myPlayerStorage.find(killer.getUniqueId());
myData.setKills(myData.getKills() + 1);
// save Data to Storage
this.myPlayerStorage.save(killer.getUniqueId());
}
// get Data temporary and Save it after finished
try (DataClosable<MyData> data = this.myPlayerStorage.findAndSave(player.getUniqueId())) {
final MyData myData = data.val();
myData.setTotalDeaths(myData.getKills() + 1);
myData.setRating(myData.getRating() - 50);
}
}