2.2 Configurable Objects

Objects can be configurable with data clusters

Configurable objects allow you to simply make any object configurable. This means you can load and save data from the object in a config design as you like.

As you can see, the data cluster plays a big part and actually holding the data, while the configurable object uses it and is controlled by a loader or saving instance.

Basic Configurable Objects

Basic configurable objects rely on the onNewConfig() and onSaveConfig() To do this, we need to do a few things.

  • Implement org.phantomapi.clust.Configurable
  • Handle the onNewConfig();
package org.phantomapi.example;

import org.phantomapi.clust.Configurable;
import org.phantomapi.clust.DataCluster;

public class ExampleConfigurable implements Configurable
{
    //Define the cluster
    private DataCluster cc;

    public ExampleConfigurable()
    {
        //Create a new cluster for this instance
        cc = new DataCluster();
    }

    @Override
    public void onNewConfig()
    {
        //Here is where we structure DEFAULTS for the config
        cc.set("something.enabled", true, "Optional\nMultiline\nComment");
        cc.set("something.name", "Configurable Name");
    }

    @Override
    public void onReadConfig()
    {
        //Called when the dataCluster has been updated with the remote
    }

    @Override
    public DataCluster getConfiguration()
    {
        //This is needed for any requests to handle this configuration object
        return cc;
    }

    @Override
    public String getCodeName()
    {
        //This is the name of the file,
        //NOT THE LOCATION
        //NOT THE EXTENSION
        return "config";
    }

}

Loading the Configuration

Loading the configuration is actually a little bit more than simply reading the config into the data cluster. Since we cannot assume what the file contains and if its YAML, or if its a folder, all of the configuration handlers deal with that. Here is the basic logic path of the configuration handlers

  • Create the file if it does not exist
  • Write all of your onNewconfig default data to a cache cluster
  • Read all of the data into the cache cluster (overwrites any defaults)
  • Saves all of the data from the cache cluster to the file

The beauty of this, is your config can have new values anytime, and it flawlessly updates. If the user were to delete a few nodes in the config, no problem. Your data cluster in the memory is used to actually use that data, but more importantly, since the defaults are overwritten, those values wont, meaning the user will get default values back for just those nodes.

package org.phantomapi.example;

import org.phantomapi.construct.Controllable;
import org.phantomapi.construct.Controller;
import org.phantomapi.construct.Ticked;

@Ticked(0)
public class ExampleController extends Controller
{
    public ExampleController(Controllable parentController)
    {
        super(parentController);

        // Create an instance of it
        ExampleConfigurable e = new ExampleConfigurable();
        loadCluster(e);
        // The configuration has been loaded.
        e.getConfiguration().getBoolean("something.enabled");
    }

    @Override
    public void onStart()
    {

    }

    @Override
    public void onStop()
    {

    }

    @Override
    public void onTick()
    {

    }
}

Field Configuration

Instead of using the data cluster to read and add values from the onNewconfig, we can use reflection instead! However this is handled by phantom with the use of annotations. In this example, we're going to make a controller configurable instead of using it in another object.

  • Define Fields
    • Must be PUBLIC
    • Must have an initial VALUE
    • Must have an @Keyed("path.in.yml")
    • Optional to have an @Comment("comment")
  • No need to use the onNewConfig or onReadConfig methods
  • Fields are read and modified via reflection
package org.phantomapi.example;

import org.phantomapi.clust.Comment;
import org.phantomapi.clust.Configurable;
import org.phantomapi.clust.DataCluster;
import org.phantomapi.clust.Keyed;
import org.phantomapi.construct.Controllable;
import org.phantomapi.construct.Controller;
import org.phantomapi.construct.Ticked;

@Ticked(0)
public class ExampleController extends Controller implements Configurable
{
    //Define it as PUBLIC and fill in the value
    @Keyed("some.name")
    public String someName = "Some Value";

    //Comment your fields
    @Comment("This is a \nMultiline Comment")
    @Keyed("some.value")
    public int someVal = 665;

    @Comment("Wrappers can be used aswell")
    @Keyed("some.wrapper")
    public Double someWrapper = 3.54543452;

    private DataCluster cc;

    public ExampleController(Controllable parentController)
    {
        super(parentController);

        cc = new DataCluster();
    }

    @Override
    public void onStart()
    {
        //Useful for loading in the plugin root 
        loadCluster(this);

        //Loads this with Plugin/folder/<code-name>.yml
        loadCluster(this, "folder");
    }

    @Override
    public void onStop()
    {

    }

    @Override
    public void onTick()
    {

    }

    @Override
    public void onNewConfig()
    {
        // Dynamic (not using it)
    }

    @Override
    public void onReadConfig()
    {
        // Dynamic (not using it)
    }

    @Override
    public DataCluster getConfiguration()
    {
        return cc;
    }

    @Override
    public String getCodeName()
    {
        return "config";
    }
}

Using MySQL Instead of YAML

Using MySQL is actually very simple since most of the information is pretty much all phantom needs. We just need to annotate our "table" and change how we load and save our data.

  • MySQL tables can change dynamically, JSON is used instead of raw data
  • MySQL loading does NOT put in defaults. You need to save after your done with it

Look at the @Tabled annotation right above the object TYPE, and look in the onStart method. Thats all you need.

MySQL is connected when either you define that you require mysql or when you try to use it. The actual database authentication is inside of the Phantom/mysql.yml configuration. This allows all phantom plugins not need to deal with connections.

package org.phantomapi.example;

import org.phantomapi.clust.Comment;
import org.phantomapi.clust.Configurable;
import org.phantomapi.clust.DataCluster;
import org.phantomapi.clust.Keyed;
import org.phantomapi.clust.Tabled;
import org.phantomapi.construct.Controllable;
import org.phantomapi.construct.Controller;
import org.phantomapi.construct.Ticked;

@Tabled("table_name")
@Ticked(0)
public class ExampleController extends Controller implements Configurable
{
    //Define it as PUBLIC and fill in the value
    @Keyed("some.name")
    public String someName = "Some Value";

    //Comment your fields
    @Comment("This is a \nMultiline Comment")
    @Keyed("some.value")
    public int someVal = 665;

    @Comment("Wrappers can be used aswell")
    @Keyed("some.wrapper")
    public Double someWrapper = 3.54543452;

    private DataCluster cc;

    public ExampleController(Controllable parentController)
    {
        super(parentController);

        cc = new DataCluster();
    }

    @Override
    public void onStart()
    {
        //Load via SQL
        loadMysql(this);

        //Or Save it
        saveMysql(this);
    }

    @Override
    public void onStop()
    {

    }

    @Override
    public void onTick()
    {

    }

    @Override
    public void onNewConfig()
    {
        // Dynamic (not using it)
    }

    @Override
    public void onReadConfig()
    {
        // Dynamic (not using it)
    }

    @Override
    public DataCluster getConfiguration()
    {
        return cc;
    }

    @Override
    public String getCodeName()
    {
        return "config";
    }
}

DataHandlers

When there is a need for multiple configurable objects of the same type to be cached and bound to another unique type of object, DataHandlers are your friend. Here is how it works

Lets make the data handler. Keep in mind, we will need to implement how to load and save the data. The rest is handled for us. Even if you request an object that isnt cached, it will be looked up, if it isnt found it will even create the defaults and load it in that way returning the configurable object. This is typically helpful when making playerdata.

The PlayerData Object

The playerdata object will store our player's data for us and deal with basic functionality if we choose to.

package org.phantomapi.example;

import org.bukkit.entity.Player;
import org.phantomapi.clust.Configurable;
import org.phantomapi.clust.DataCluster;
import org.phantomapi.clust.Keyed;
import org.phantomapi.clust.Tabled;

//This is not needed for yml configs, but
//In case we ever choose to use SQL, its here
@Tabled("player_title_data")
public class PlayerData implements Configurable
{
    private Player player;
    private DataCluster cc;

    @Keyed("cool.title")
    public String cooTitle = "Title";

    public PlayerData(Player player)
    {
        this.player = player;
        this.cc = new DataCluster();
    }

    @Override
    public void onNewConfig()
    {
        // Dynamic
    }

    @Override
    public void onReadConfig()
    {
        // Dynamic
    }

    @Override
    public DataCluster getConfiguration()
    {
        return cc;
    }

    @Override
    public String getCodeName()
    {
        //This makes each config from this type different 
        //Based on the player's uuid.
        return player.getUniqueId().toString();
    }

}

The Data Handler

Lets create a controller for this. Since we need to know what to do when to stop, and what to do when we start. More specifically there are a set few helpers for this. We will be using the DataHandler which is actually a controller by itself.

package org.phantomapi.example;

import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerQuitEvent;
import org.phantomapi.clust.DataController;
import org.phantomapi.construct.Controllable;

//Notice we add <PlayerData, Player>
//This means we want the configurable object to be PlayerData.class
//And we want the identifier to be a Player.class type
public class PlayerDataController extends DataController<PlayerData, Player>
{
    public PlayerDataController(Controllable parentController)
    {
        super(parentController);
    }

    @Override
    public PlayerData onLoad(Player identifier)
    {
        //This is called when we need to load a playerdata instance
        //We are given a player identifier

        PlayerData pd = new PlayerData(identifier);
        loadCluster(pd);

        return pd;
    }

    @Override
    public void onSave(Player identifier)
    {
        //Called when we need to save a player
        //We can use the get() method to get it from the cache

        saveCluster(get(identifier));
    }

    @Override
    public void onStart()
    {

    }

    @Override
    public void onStop()
    {
        //To save all of the data in the cache
        saveAll();
    }

    @EventHandler
    public void on(PlayerQuitEvent e)
    {
        //Save the player when they quit
        save(e.getPlayer());
    }
}

Do not load clusters. There is no need to. Since when you call get(Player) if its not cached, it will be loaded and returned. Just be sure to save it onStop() and on player quits

results matching ""

    No results matching ""