-
Notifications
You must be signed in to change notification settings - Fork 3
Using the Profile Cache
The profile caching system is fail-safe, asynchronous, and the primary feature of Payload.
To get started with a Profile Cache, you first need to make your own Profile object to store data in:
import com.jonahseguin.payload.profile.profile.PayloadProfile;
public class MyProfile extends PayloadProfile {
private double balance = 100.0; // dollars
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
Your profile object can have any properties in it and it will be automatically mapped and persisted to/from MongoDB & Redis. If you with to make an object non-persistent, just use the transient
classifier on your variable.
We use Morphia for our MongoDB ORM, so any features within that you can take advantage of in your object. Such as @PostPersist, @PrePersist, etc.
Next, we need a manager / instance of own Profile Cache. I recommend having a dedicated class for this:
import com.jonahseguin.payload.common.cache.CacheDatabase;
import com.jonahseguin.payload.profile.cache.PayloadProfileCache;
import com.jonahseguin.payload.profile.util.ProfileCacheBuilder;
import com.jonahseguin.payloadtest.MyDebugger;
import org.bukkit.plugin.java.JavaPlugin;
public class MyProfileCache {
private final JavaPlugin instance; // An instance of our JavaPlugin to register with Payload
private final PayloadProfileCache<MyProfile> cache; // Our cache. Notice the <MyProfile> -- Replace that with your object to persist.
public MyProfileCache(final JavaPlugin instance, final CacheDatabase database) {
this.instance = instance;
this.cache = new ProfileCacheBuilder<MyProfile>(instance)
.withDatabase(database) // Our CacheDatabase contains our MongoDB, Redis(jedis), and Morphia objects. It can be shared among different cache objects
.withEnableAsyncCaching(false) // Allow the player to login before our caching is complete, loads their data in the background
.withCacheFailRetryIntervalSeconds(60) // How often (seconds) to retry caching for players that have failed to cache
.withHaltListenerEnabled(true) // Enable Halt Listener for failures
.withCacheLogoutSaveDatabase(true) // Save their data to the database when they logout (Recommended: true)
.withCacheRemoveOnLogout(false) // Remove their data from the local cache on logout? (Recommended: false)
.withCacheLocalExpiryMinutes(30) // How long (minutes) to wait before removing a player's data from the cache after they logout
.withProfileClass(MyProfile.class) // Our Profile object class
.withDebugger(new MyDebugger()) // We are using the same Debugger we used in example Object Cache
.withInstantiator(MyProfile::new) // Instantiate: takes arguments (username: string, uniqueId: string) -> return a new instance of your Profile object
.withRedisKey("my.profile.") // Prefix for redis key to store profiles @ (by UUID) ex/ KEY my.profile.some-long-uuid
.build();
if (!cache.init()) {
instance.getLogger().warning("Failed to initialize cache for MyProfile!");
}
}
}
Notice that we are using our MyProfile
Profile object. We are also using a CacheDatabase
object that you will need to initialize with valid information and active connections. All of the settings in the builder are customizable to your needs, and you don't have to use the builder of course. Simply implement PayloadCacheSettings
.
Also notice that we used a MyDebugger
for our debugger. The debugger instance handles all errors within your Payload Cache instance -- and without it (the default one is empty) you will suppress all your errors, which will make debugging a nightmare!
Implement a debugger:
import com.jonahseguin.payload.common.cache.CacheDebugger;
import io.sentry.Sentry;
import io.sentry.event.Event;
import io.sentry.event.EventBuilder;
import io.sentry.event.interfaces.ExceptionInterface;
import org.bukkit.Bukkit;
public class MyDebugger implements CacheDebugger {
// We are using Sentry.io -- an error capturing / reporting library. It's basically free for students with the GitHub developer pack. https://education.github.com/pack
// However you can handle errors however you want (logging to console +/ alerting admins is fine!)
@Override
public void debug(String s) {
Sentry.capture(s);
}
@Override
public void error(Exception e) {
Sentry.capture(e);
}
@Override
public void error(String s) {
Sentry.capture(s);
}
@Override
public void error(Exception e, String s) {
capture(e, s);
}
@Override
public boolean onStartupFailure() {
Sentry.capture("Startup failed for Payload cache!");
return true; // Return whether or not to shut down the cache on failure (True = shutdown)
}
public static void capture(String message) {
// using Sentry.io for error capturing
Bukkit.getServer().getLogger().warning("[Error] " + message);
Sentry.capture(
new EventBuilder()
.withMessage(message)
.withLevel(Event.Level.ERROR)
);
}
public static void capture(Throwable throwable, String message) {
// using Sentry.io for error capturing
Bukkit.getServer().getLogger().warning("[Error] " + message);
Sentry.capture(
new EventBuilder()
.withMessage(message)
.withLevel(Event.Level.ERROR)
.withSentryInterface(new ExceptionInterface(throwable))
);
}
}
It is very important that you call cache.init()
which returns a boolean (True if initialization was successful). If it fails, I'd recommend disabling public logins to your server or shutting down or disabling the plugin -- as you don't want unauthorized logins.
Notice that we called cache.init()
near the end of our constructor call:
if (!cache.init()) {
instance.getLogger().warning("Failed to initialize cache for MyProfile!");
}
And when your plugin disables (onDisable()
), be sure to call cache.shutdown()
to cleanup and save all profiles.
Now we're ready to start using profiles! Payload takes care of the rest.
Methods you can use include:
- Get a profile:
cache.getProfile()
- Get a profile from a specific layer (local/redis/mongo):
cache.getLayerController().getLayer(PCacheSource.REDIS).get(uuid)
Note: to obtain a UUID <-> Username conversion, you can utilize the USERNAME_UUID layer. - Save a profile:
cache.saveEverywhere()
You can also do a lot more with the Profile controllers, different layers, etc.
Payload also features rich events for hooking into when things happen within Payload.
Events for the Profile Cache are in the com/jonahseguin/payload/profile/event
package.
They include:
- PayloadProfileInitializedEvent
- PayloadProfileLoadedEvent
- PayloadProfilePreQuitSaveEvent
- PayloadProfilePreSaveEvent
- PayloadProfileSavedEvent (after saved)
Enjoy! Any questions, comments, or concerns, don't hesitate to contact me, open a PR, or open an issue. Thanks!
Developed by Jonah Seguin
Visit my website @ jonahseguin.com to contact me if you are interested in working with me.
Copyright (c) 2018 Jonah Seguin. All rights reserved.
asynchronous fail-safe profile & object caching
Read First: Getting Started
How To: Profile Cache
How To: Object Cache
by Jonah Seguin
Copyright (c) 2018 Jonah Seguin. All rights reserved.