Help with Persistance

Discussion in 'Plugin Development' started by LRFLEW, Aug 26, 2011.

Thread Status:
Not open for further replies.
  1. Offline

    LRFLEW

    I am working on a plugin that stores where a player uses a teleport command so it can teleport them back to where they were. I need this information to persist between restarts, and it seems that the default persistence system built into Bukkit is the way to go. I have never dealt with persistence before, and I have no clue where to start.

    The information that I need to persist is currently being stored as a HashMap<Player, Location>, fyi
     
  2. Offline

    DrBowe

    I find it much easier to just use objects that are serializable, and then store them on a flat-file. For storing locations, I use HashMap<String, SerializableLocation>. With the String being the player's name, and SerializableLocation being something I made for use of my compass plugin:

    In case you want to use it:
    Code:java
    1.  
    2.  
    3. package me.DrBoweNur.ComPassionate;
    4.  
    5. import java.io.Serializable;
    6.  
    7. import org.bukkit.Location;
    8. import org.bukkit.Server;
    9.  
    10. public class SerializableLocation implements Serializable{
    11. static final long serialVersionUID = 123928345;
    12.  
    13. private double xCoor;
    14. private double yCoor;
    15. private double zCoor;
    16. private String worldName;
    17.  
    18. public SerializableLocation(Location loc){
    19. this.xCoor = loc.getX();
    20. this.yCoor = loc.getY();
    21. this.zCoor = loc.getZ();
    22. this.worldName = loc.getWorld().getName();
    23. }
    24.  
    25. public Location toLocation(Server s){
    26. return new Location(s.getWorld(worldName), xCoor, yCoor, zCoor);
    27. }
    28. }
    29.  


    Its easy enough to convert between this and a normal location, on restarts/reloads. I also know that I'm not the first to do this, so if someone else has a better Location class that is serializable, they can post it here too. :D
     
  3. Offline

    LRFLEW

    1) Never used a "flat file" before. How would I go about doing that? would a YML file or XML work better? (just a thought)
    2) Your SerializableLocation is missing Pitch and Yaw :p
    3) what is serializable and how does it work :p
     
  4. Offline

    DrBowe

    1) A flat file is just about any file that isn't an overhauled database. I prefer the .bin, as I've never really had a need for anything other than it.

    2) Like I said, I custom built it for my plugin. I didn't need pitch and yaw, just the location in general :p

    3) Serialization is how things are saved onto files and loaded back from them. Anything that needs to be saved MUST implement serializable, and be comprised of values that also implement serializable (All primitive types implement it) . Essentially, you must find a way to convert Bukkit objects into primitives, so you can save and load them properly.
     
  5. Offline

    LRFLEW

    would it not work if I made a serializable class extending Location and use custom
    Code:
    private void writeObject(java.io.ObjectOutputStream out)
         throws IOException
     private void readObject(java.io.ObjectInputStream in)
         throws IOException, ClassNotFoundException;
    functions to serialize it once it's needed to be written? :p

    EDIT: or heck, a serializable HashMap :p
     
  6. Offline

    DrBowe

    I don't believe so, but I'm no expert on serialization. From what I understand, if it contains any aspect of the non-serializable class, it will not work properly. The only reasons things like toLocation work, is because they dont actually save any Location, they only 'make' one when the server is active.
     
  7. You could use Inbuilt bukkit persistance (Though ive never been able to make it work)
     
  8. Offline

    bergerkiller

    @DrBoweNur to what degree is a serializable class different from storing the class members using dataoutputstreams/datainputstreams?
    [​IMG]
    Pic related, it is how I store my data.
     
  9. Offline

    DrBowe

    @bergerkiller
    To be honest, I really don't know. I only have a background of one AP Computer Science class, so serialization was new to me when I started making plugins. That could be a better way of doing it, but I'm not the one to make the call :p
     
  10. Offline

    LRFLEW

    That was what I was thinking when I started this thread, but nobody seems to know how to use it :p

    From what I've seen, nothing. Implementing the Serializable interface just adds the functions
    Code:
    private void writeObject(java.io.ObjectOutputStream out)
         throws IOException;
     private void readObject(java.io.ObjectInputStream in)
         throws IOException, ClassNotFoundException;
    to the object (you can define these functions yourself or not). You then use these functions to read and write the object. That's my understanding.
     
  11. If you wanna use the Bukkit Persistance System create a class that maps your database exactly with private variables. Lets your IDE autogenerate the getters and setters for the variables or creates them yourself but it is important that the setters and getters are named exactly like your private var.

    ex.:
    Code:
    private String username;
    
    public void setUsername(String username);
    
    public String getUsername();


    Then in your main class call setupDatabase() onLoad():


    Code:
    private void setupDatabase() {
            try {
                getDatabase().find(DBLevelup.class).findRowCount(); // edit DBLevelup.class to match your class mapping the database
            } catch (PersistenceException ex) {
                RCLogger.info("Installing database for " + getDescription().getName() + " due to first time usage");
                installDDL();
            }
        }
        @Override
        public List<Class<?>> getDatabaseClasses() {
            List<Class<?>> list = new ArrayList<Class<?>>();
            list.add(DBLevelup.class); // same here
            return list;
        }
    When you want to access the database do a call like this:


    Code:
    private void loadLevelDatabase() {
    // searches for the playername in the databse (can only exist once)
    lvldb = RCPlayer.plugin.getDatabase().find(DBLevelup.class).where()
    .ieq("playerName", this.player.getName()).findUnique();
    // if player joined first time create new colum with standard variables
    if (lvldb == null) {
    lvldb = new DBLevelup();
    lvldb.setPlayer(player);
    if (getCanLevel()) {
    lvldb.setLevel(0);
    } else {
    setLevel(-1);
    lvldb.setLevel(getLevel());
    }
    lvldb.setExpToNextLevel(getExpToLevel(getLevel() + 1));
    lvldb.setExp(0);
    lvldb.setJoined(this.lastJoinDate);
    lvldb.setSkillpoints(0);
    lvldb.setSkillResetCount(0);
    lvldb.setSkillCount(skills.size());
    lvldb.setSpendSkillpoints(getSpendSkillpoints());
    }
    }

    my leveldb.java looks like this:

    Code:
    /**
    *
    * @author Silthus
    *
    */
    @Entity()
    @Table(name = "rcs_level")
    public class DBLevelup {
    
    @Id
    private int id;
    @NotNull
    @Length(max = 32)
    private String playerName;
    private int level;
    private int exp;
    private int expToNextLevel;
    private String joined;
    private int skillpoints;
    private int skillCount;
    private int skillResetCount;
    private int spendSkillpoints;
    
    public void setId(int id) {
    this.id = id;
    }
    
    public int getId() {
    return id;
    }
    
    public String getPlayerName() {
    return playerName;
    }
    
    public void setPlayerName(String player) {
    this.playerName = player;
    }
    
    public Player getPlayer() {
    return Bukkit.getServer().getPlayer(playerName);
    }
    
    public void setPlayer(Player player) {
    this.playerName = player.getName();
    }
    
    public void setLevel(int level) {
    this.level = level;
    }
    
    public int getLevel() {
    return level;
    }
    
    public void setExp(int exp) {
    this.exp = exp;
    }
    
    public int getExp() {
    return exp;
    }
    
    public void setExpToNextLevel(int expToNextLevelUP) {
    this.expToNextLevel = expToNextLevelUP;
    }
    
    public int getExpToNextLevel() {
    return expToNextLevel;
    }
    
    public void setJoined(String date) {
    this.joined = date;
    }
    
    public String getJoined() {
    return joined;
    }
    
    public void setSkillpoints(int skillpoints) {
    this.skillpoints = skillpoints;
    }
    
    public int getSkillpoints() {
    return skillpoints;
    }
    
    public void setSkillCount(int skillCount) {
    this.skillCount = skillCount;
    }
    
    public int getSkillCount() {
    return skillCount;
    }
    
    public void setSkillResetCount(int skillResetCount) {
    this.skillResetCount = skillResetCount;
    }
    
    public int getSkillResetCount() {
    return skillResetCount;
    }
    
    /**
    * @param spendSkillpoints the spendSkillpoints to set
    */
    public void setSpendSkillpoints(int spendSkillpoints) {
    this.spendSkillpoints = spendSkillpoints;
    }
    
    /**
    * @return the spendSkillpoints
    */
    public int getSpendSkillpoints() {
    return spendSkillpoints;
    }
    
    }
    hope that helps.
     
  12. Offline

    LRFLEW

    @silthus
    1) ack :O
    2) do I have to use getters and setters? Could I just make the variables public (or protected) instead?
    3) what exactly is loadLevelDatabase() doing? where do I call it?
     
  13. 1) what means ack? ^^
    2) yes you need to use getters and setters
    3) it registers the driver and loads the class via reflection (I think ^^).
    you need to call in onEnable();
     
  14. Offline

    LRFLEW

    @silthus
    I've had a chance to look over the code again, and I think I understand what you are doing (I like to see what the code does before implementing it so if there's a bug or a faster way of doing it, I know where to go). When you said to call setupDatabase() in onLoad(), did you mean onEnable(), or do you want me to call it onLoad()? As for the confusion with loadLevelDatabase(), you accessed a variable "this.player" which threw me off :p. It appears it's going through the list of instances of the class in the ebean server and finding the one with the player variable equal to the player's name. Wouldn't be easier to find/load a single instance of a HashMap on enable, and access that when I need to find the class associated with the player?

    @silthus
    Ok, as to my understanding I had when I wrote the paragraph above, it was false understanding. I tried to make a test plugin just to play with persistence, but I ended up way confused.

    If anybody can explain this in a "this is here because" fashion, I will be ecstatic :).

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 19, 2016
  15. Offline

    HansAnderson

    Flat files are a very easy way to start. And examples are a great way to learn. This is an old plugin called SignReadMore by Milton, whose source is here. Look mostly at loadSavedCustomSigns and saveCustomSignsToFile.

    To store the x, y, and z coordinates, just add them to the file with the player name string first followed by some very obvious separator (list ENDPLAYERNAME), then the x y z coordinates separated by commas, then a newline. When you read in from the file, you'll know exactly how to reconstruct the information based off of your separators.
     
Thread Status:
Not open for further replies.

Share This Page