[INACTIVE][DEV] Persistence v0.76 - Data-Driven Bukkit [677]

Discussion in 'Inactive/Unsupported Plugins' started by NathanWolf, Jan 29, 2011.

  1. Offline

    NathanWolf

    Persistence - Data-Driven Bukkit

    PLEASE NOTE
    Persistence is a developer API and framework- NOT a game or admin plugin.

    If you've come here looking for plugin/admin support for Spells, Wand, NetherGate or any other plugin that uses Persistence- please turn around and go back to that thread. Thank you for your cooperation!

    If you're a dev, then check the Persistence wiki for information on using the Persistence framework.

    View changelog on github
     
    Duccis likes this.
  2. Offline

    NathanWolf

    Ok, big news, but I'm super hungry- will provide more details later.

    Basically, if you're using Persistence, you now support Permissions for free. Don't say I never did nothin' for ya!

    Here's the changelog for 0.27, until I get time to get back in here- I have updated the javadocs as well.


    # 0.27
    - Don't persist command.children, it can be built at runtime from the parents.
    - Permissions integration (Permissions.hasPermission, and CommandData.permissionNode)
    - Remove NetherGate-specific stuff from WorldData- my bad! I promise not to abuse my dual dev/client status in the future :)
    --- merged: Feb 9, 2011 2:16 AM ---
    Oh, I haven't updated the jar yet, either- just the source. I need to fix NetherGate, I broke it ...

    Speaking of breaking things.. yeah... sorry, you probably have to delete global.db! :(
    --- merged: Feb 9, 2011 3:10 AM ---
    Ok, a bit more on this-

    If you use Persistence, and you use the command-dispatch system, as of 0.27 you can say that your plugin supports Permissions. Because it does :)

    By default, permission nodes are auto-generated for your commands like so:
    Code:
    <plugin>.commands.<command>.<subcommand>...
    And so on. Also, and this is important and I'm open to feedback- by default, if Permissions is not present, anyone can use your plugin's commands.

    I'm assuming that's what we want. If it's not what you want, there are variants of getGeneralCommand and getPlayerCommand that take a PermissionType value- there are few different ways you can tell Persistence to handle permissions for a command:
    • DEFAULT : Do a normal permissions check, allow if Permissions is missing.
    • ALLOW_ALL : Always allow use, bypass permissions
    • OPS_ONLY : Only allow players with isOp to use this, regardless of Permissions
    • AMDINS_ONLY : Same as DEFAULT, but disallow if Permissions is missing. This is what Persistence uses internally for all its commands except "su" (see below)
    I'm hoping that covers all the bases- let me know if you think otherwise.

    You can also override the default node generated for your command.

    If you want to use more fine-grained permissions control, you can still stay within Persistence- just use persistence.hasPermission().

    There is another main benefit to you going through Persistence for your permissions- and this is both for you, and your admins- that is the "/su" player command.

    This is a base command, and you must be an Op to use it. It toggles "superUser" mode for your player- while you're a super user, you bypass all permission checks.

    This lets server admins set themselves up as a normal user, but still be able to admin when they need to.

    In particular, I want this functionality because my long-term plans include an Experience plugin that would use user groups to auto-progress players based on achievements. And, one day, when I'm done coding, I'd actually like to play this game again :) As a normal user, who has to progress through the ranks like everyone else...

    Anyway, that's about all there is to the Permissions integration. I think it's pretty clean, and should also mean your plugins will transition to the new internal Bukkit permissions system completely transparently when it's ready.

    I haven't done a lot of testing on this yet, but I wanted to put it out here so you knew it was here, in case you were working on Permissions integration yourself- I may be able to save you some time :)
    --- merged: Feb 9, 2011 3:11 AM ---
    Oh, one more note- and again, haven't tested this much, but /phelp should respect Permissions if present, and only show players commands they have access to.

    I plan on changing it from a hierarchical list to a paged list of alphabetical commands. (There will then be a sendPage(x) type of function for Message!)
     
  3. Offline

    amkeyte

    Nice! I'll probably be playing with this some tonight.
     
    NathanWolf likes this.
  4. Offline

    NathanWolf

    Cool! Let me know how you get on with it - it should be pretty easy to get going, I've updated all my sample code in the OP (I think)
    --- merged: Feb 9, 2011 3:44 AM ---
    A quick note on performance

    I thought it would be good to spell out a few key concepts in Persistence:
    • get(), remove() and put() are meant to be very efficient, probably in that order. Once instance merging is complete put() will be less efficient if you add new instances (with id conflicts), but still very fast if you just put back the same instance you got with get().
    • save() is meant to be reasonably efficient, as long as there is no data to save.
    • save() may be very expensive, depending on how much dirty data there is, how many of those dirty objects have lists, etc.
    Currently, Persistence will internally issue a save() on server shutdown and player login/logout.

    The former should be obvious, the latter two are mainly to track player data, but also login/logout seems like a reasonable time to save every once in a while.

    Now, ultimately, when the scheduled event interface is done (?) for Bukkit, I want Persistence to do incremental auto-saves on a configurable timer.

    At that point, I may remove access to save() entirely.

    So, basically, I would avoid using it if you can. If you don't think you can live with your data not saving until a player login/logout, then use save() with discretion.

    It's important to note that, other than player login/logout and plugin disable, Persistence has no listeners. It's never doing anything in the background, other than (potentially) eating some extra memory. This will change, of course, once auto-save is implemented- but then you'll be able to turn that off to see if that is what's causing lag.

    As an example use-case, NetherGate uses a single playerMove listener. It first pulls a PlayerData reference from Persistence- so, one get() per playerMove. I'm going to change this, but not for performance reasons- I'm currently using PlayerData as NetherGate's own personal "last player position", but that's not good practice- another plugin could easily call playerData.update(player) and block my ability to see player position changes.

    Anyway, that's one get() per onPlayerMove, and it doesn't seem to have any noticeable impact.

    I then call playerData.update(player), and put() the player data back, to save the last location. Again, I'm going to move where I store this data- but it'll still be one put() and one get() each and every player move event.

    So far, so good, in terms of performance.

    After that, I use that player data (as I mentioned) comparing with current player location to see if the player has changed blocks (Bukkit, unlike hMod, sends move events for intra-block movement).

    I then do another get() for TeleportingPlayer, NetherGate's extension of player data.

    I then do some checking based on the player's teleporting state and what type of block they moved into, and basically I may or may not end up modifying and putting() the teleporting player data if the player needs a state change.

    So, I try to avoid calling put() unless really necessary, try to avoid get()'ing data until I really need it, etc. And I don't issue a save()- there's no reason to.

    And it seems to perform just fine on my public server, which is over a decade old :)
     
  5. Offline

    amkeyte

    alright, now with the permissions support, I think I'll be changing my command structure.

    So far I have a few commands like this, where there is "ss" the general command, "phase" as the sub command and <help|generate|explore|mine|pvp|raid|tally|post> as a parameter. If I understand correctly, if I were to convert the last parameter to sub commands then I would have automatic Permissions for each action.

    Code:
            PluginCommand phase = generalCommand.getSubCommand("phase", "set the SkinsAndShirts game phase", "ss phase <help|generate|explore|mine|pvp|raid|tally|post>");
            phase.bind("onCommandPhase");
    Is this correct? Also using those as sub commands would allow me to add better help per command. In hindsight, I suppose those should be implemented as sub commands in any case, since they really are not "parameters" per se.

    Answered my own question I guess lol

    Well, I finally got my main converted over. Can't run it just yet, but I will report with results when I have them. Dunno if you have room in the OP, but it might be handy to post the import line for some of the classes used in the examples. I had to do a little hunting around to find them :)

    <---LAZY GUY! hahaha
     
  6. Offline

    NathanWolf

    I think you've got the right idea, and yes- that means each of your sub-commands automatically has its own Permissions node. But it seems like you already figured that out :)

    And, yes- one of the main ideas is to provide better help per sub-command. I don't like the way you're supposed to cram that all in the info for the parent command. Persistence keeps it much cleaner, and lets you get help for an entire tree of commands, or just one command (with or without extended usage info in both cases).

    Speaking of extended usage info- that's a list, by the way. The one you pass in to getCommand is actually optional (you can pass null), and will just be the first element of the list.

    Feel free to add more usage lines if there are multiple ways of using one of your commands, but it still doesn't make sense to further divide that command into sub-commands.

    I think it's CommandData.addUsage(String usage)

    Well, there are the javadocs! :) Seriously, though, I use Eclipse- so I guess I don't notice things like this. I just click the little yellow button and let it import classes for me ;)

    As soon as I get things stabilized I'm going to put up a complete sample plugin, that should help get you started a lot easier.
    --- merged: Feb 9, 2011 5:06 AM ---
    BTW, one thing I want to mention, because I've found myself doing it from time to time...

    If you are trying to access persisted data somewhere that would be difficult to pass the Persistence instance down to (and you haven't already just put a static accessor on your plugin or something), you can use Persistence.getInstance() to retrieve the persistence instance.

    It's a singleton, but it does get initialized by the Persistence plugin in onEnable- so try not to access it during onEnable, I guess- anytime after that should be fine.
    --- merged: Feb 9, 2011 5:09 AM ---
    Hm, now that I type that last part, that sounds like a nasty race condition... I've had no problems accessing persistence in my onEnable from NetherGate, but I think I need to investigate this and make sure Persistence always gets initialized first.

    Actually, it might not matter, unless you're trying to access the global data types- I think that's all the plugin really initializes within Persistence itself.

    It is something I'm going to have to find a way to address, though- I plan on using common data in my onEnable to set up references to materials and material lists, once that functionality is in place.

    I don't really want to initialize in the constructor- that seems like a bad idea, like it would initialize even if the plugin was disabled. I'll think on it.
     
  7. Offline

    xZise

    How does the plugin react, if I want to upgrade my data structures. For example if I want to change a name of a field within the object or the type of a field.

    Fabian
     
  8. Offline

    ShadowOfLegends

    Hi!
    Each time I try to start my server with this plugin, I get
    2011-02-09 00:35:45 [INFO] Persistence version 0.27 failed to initialize
    java.lang.StackOverflowError
    at com.elmakers.mine.bukkit.plugins.persistence.dao.PluginCommand.getSubCommand(PluginCommand.java:124)
    at com.elmakers.mine.bukkit.plugins.persistence.dao.PluginCommand.getSubCommand(PluginCommand.java:124)
    ....etc....

    I have no other plugins active now, I disabled them all...

    Using java 64 bit, windows 7 64 bit... grabbed the newest craftbukkit i could find :-/

    HALP
     
  9. Offline

    NathanWolf

    SO sorry about that! I should not have pushed that code so soon- it's fixed now.
    --- merged: Feb 9, 2011 7:04 AM ---
    That is an excellent question- right now "not well at all" :)

    On the roadmap is automatic data migration- I'll be able to handle simple changes like column drop/add/rename or data type changes. I won't ever be able to handle anything complex, such as you split up a class into two classes or something.
     
  10. Offline

    frog

    Hi, I am having the following issue with Persistence failing to initialize.
    All other plugins disabled.
    Using the latest Persistence jar at time of posting.
    Code:
    2011-02-09 11:54:20 [INFO] This server is running Craftbukkit version git-Bukkit-0.0.0-371-gd3a87e2-b285 (MC: 1.2_01)
    2011-02-09 11:54:20 [INFO] Preparing level "World5"
    2011-02-09 11:54:20 [INFO] Preparing start region
    2011-02-09 11:54:21 [INFO] Preparing spawn area: 73%
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.sender
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.plugin
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.pluginAuthors
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.pluginCommands
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.pluginMessages
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.message
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.command
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.commandUsage
    2011-02-09 11:54:23 [INFO] Persistence: Created table global.commandSenders
    2011-02-09 11:54:23 [INFO] Persistence version 0.27 failed to initialize
    java.lang.StackOverflowError
    2011-02-09 11:54:23 [INFO] Done! For help, type "help" or "?"
    64bit Java 1.0.7 on Linux.
     
  11. Offline

    NathanWolf

    This is fixed in source- I thought had pushed an updated zip last night, but my NetherGate peeps are telling me no

    So, I guess I published a version of Persistence with an infinite recursion bug... really sorry about that, that's so super lame of me.

    I'm going to get a real fixed update out soon- you guys seem to be handling this surprisingly well, but the NetherGate crowd is getting very restless :(
     
  12. Offline

    DREAD_XI

    So question, how do I get this to work on my server and change worlds back and fourth. I tested dinnerbone and I can switch to nether and back but I just want to be able to type a command to switch threw different worlds that are normal. It would be fun to still do the nether thing from time to time but my users want to be able to have multiworlds. I am not new to building servers and just re opened my server with craftbucket and all plugins are working. I on the other hand no nothing about code and just follow directions when it comes to any of that. Thank you for any help.
     
  13. Offline

    NathanWolf

    Argg... apparently, I also forgot to push my last commit- I think I had already pushed since fixing infinite recursion, but if not... well, now it's fixed in the source :(

    I was really batting 1000 last night! :\
    --- merged: Feb 9, 2011 2:50 PM ---
    This is actually supported really well in Persistence. It does not auto-create worlds for you, but that's pretty easy to do- you can either look at the NetherGate source, or just look at PluginUtilities . getWorld/createWorld.

    NetherGate maintains some data to link worlds together- I had originally put this in WorldData, but that's really NetherGate-specific. There's no saying that some other portal plugin wants the concept of worlds linked together by default...

    However, if you want to just set up a plugin of your own to switch back and forth between worlds, that should be pretty easy- just use getAll(list, WorldData.class) (again, see NetherGate) then use GetWorld() on the current world, find it in the list, go to the next one, tp the player there, etc.

    It's really a bit complex if you want to do it right- you have to wait for the other chunk to load, and then find a good place to stand.

    Anyway- if what you were really after here has nothing to do with dev, and you literally just "want a command to switch worlds", then check out NetherGate :) It has a "/nether go" command that may be relevant to your interests.... (as well as a "/nether create world <name> normal" for creating extra non-nether worlds)

    Persistence is a [DEV] Plugin, it doesn't get used on servers directly as a gameplay plugin.
    --- merged: Feb 9, 2011 3:53 PM ---
    Ok, 0.30 is "official"- jar and source. It should fix any lingering problems from my brain-melt last night.

    DEVS - PLEASE UPDATE!

    Sorry! Storing objects with objects as ids that also have object ids got very complicated :(

    That's no excuse for that infinite recursion thing- that was a matter of me accidentally committing before testing. It was a one-line function that was supposed to call a variant of that function, but instead just called itself. Bleh.
     
  14. Offline

    frog

    The .jar download is still 0.27 at this time.
     
  15. Offline

    NathanWolf

    Sorry! So many moving parts.... should be updated now.
     
  16. Offline

    DREAD_XI

    I do not know code at all..... This is something I do not do. So I guess I am just requesting a plugin now. I have your nethergate it creates worlds fine, I cannot join them. It gives me failed to teleport. Thanks for the help.
    --- merged: Feb 9, 2011 5:23 PM ---
    Just got your update to permissions and now everything works!!!!!!!!!!!!!!!!!!!!!!!!!
    TYTYTYTYTYTYTY
    I will be sure to post if anything changes.
     
  17. Offline

    NathanWolf

    Sweet! Glad to hear it. I pretty much broke everything last night, pretty bad- things are getting back together now, though.
    --- merged: Feb 10, 2011 1:26 AM ---
    A few Persistence releases have gone by with not much news- things are really starting to feel pretty stable (as long as I don't write any more infinite recursion loops...)

    Seriously, though, I'm getting really happy with it. Mainly, my time has been consumed with getting objects-as-ids working, which has really turned out to be a tremendous pain in the butt.

    One thing I want to point out is that if you have an object as an id, you can look up an instance to your object either using the "runtime" id - an instance of whatever your id is, or the "concrete" or "data" id, which is basically whatever the primitive type is, as deep down the id chain as I need to go to field a real piece of data.

    This all seems to be working pretty well- I've got some complex stuff going on in NetherGate, and soon I'll be using a BlockVector as an id. I'm already using PlayerData as an id- recommend a YourPluginPlayer class for any data you want to save, give it a PlayerData id and use schema.player as the name- it's nice and consistent :) Up to you, of course.

    And, if you haven't explored "/persist list" yet, please update and do so! I made it awesome.
     
  18. Offline

    Normandy

    I have persistence installed to use NetherGate, it is great. However, I just installed iConomy and when I type in "/plugin list" to see if everything is okay my game crashes. I am guessing it is persistence because of the last couple of lines in the server log labelled with (B) ( /fail bold lol).

    EDIT: The same error occurs with LevelCraft.
    EDIT: Seems like the error is just occurring no matter the plugin.

    Code:
    --- BEGIN ERROR REPORT a1dce528 --------
    Generated 2/9/11 8:47 PM
    Minecraft: Minecraft Beta 1.2_02
    OS: Windows 7 (x64) version 6.1
    Java: 1.6.0_23, Sun Microsystems Inc.
    VM: Java HotSpot(TM) Client VM (mixed mode), Sun Microsystems Inc.
    LWJGL: 2.4.2
    OpenGL: GeForce GTX 280/PCI/SSE2 version 3.3.0, NVIDIA Corporation
    
    java.lang.StringIndexOutOfBoundsException: String index out of range: 77
        at java.lang.String.charAt(Unknown Source)
        at nh.a(SourceFile:152)
        at nh.a(SourceFile:119)
        at pe.a(SourceFile:238)
        at ll.b(SourceFile:346)
        at net.minecraft.client.Minecraft.run(SourceFile:668)
        at java.lang.Thread.run(Unknown Source)
    --- END ERROR REPORT d01f4c76 ----------
    And here is the server log:
    Code:
    2011-02-10 01:47:13 [INFO] Starting minecraft server version Beta 1.2_01
    2011-02-10 01:47:13 [INFO] Loading properties
    2011-02-10 01:47:13 [INFO] Starting Minecraft server on *:25565
    2011-02-10 01:47:13 [INFO] This server is running Craftbukkit version git-Bukkit-0.0.0-360-gf5b151f-b271 (MC: 1.2_01)
    2011-02-10 01:47:13 [INFO] Preparing level "world"
    2011-02-10 01:47:13 [INFO] Preparing start region
    2011-02-10 01:47:14 [INFO] Preparing spawn area: 69%
    2011-02-10 01:47:15 [INFO] [Cleaner] version [1.6] (Night) loaded
    2011-02-10 01:47:15 [INFO] [iChat] version [1.5] (Maria Holic) loaded
    2011-02-10 01:47:15 [INFO] [iConomy] version [2.2] (Aime) loaded
    2011-02-10 01:47:15 [INFO] [General] version [2.1] (Salvez) loaded
    2011-02-10 01:47:15 [INFO] [Permissions] version [2.0] (Handler) loaded
    2011-02-10 01:47:15 [INFO] WorldGuard 3.2.2 loaded.
    2011-02-10 01:47:15 [INFO] WorldGuard: Permissions plugin detected! Using Permissions plugin for permissions.
    2011-02-10 01:47:15 [INFO] WorldGuard: Single session is enforced.
    2011-02-10 01:47:15 [INFO] WorldGuard: TNT ignition is PERMITTED.
    2011-02-10 01:47:15 [INFO] WorldGuard: Lighters are PERMITTED.
    2011-02-10 01:47:15 [INFO] WorldGuard: Lava fire is blocked.
    2011-02-10 01:47:15 [INFO] WorldGuard: Fire spread is UNRESTRICTED.
    2011-02-10 01:47:15 [INFO] WorldEdit 3.2.2 loaded.
    2011-02-10 01:47:15 [INFO] WorldEdit: Permissions plugin detected! Using Permissions plugin for permissions.
    2011-02-10 01:47:15 [INFO] [Achievements-v0.3b] Found required plugin: Stats
    2011-02-10 01:47:15 [INFO] [Achievements-v0.3b] loaded 63 achievements definitions
    2011-02-10 01:47:15 [INFO] [Achievements-v0.3b] Achievements v0.3b Plugin Enabled
    2011-02-10 01:47:15 [INFO] NetherGate version 0.16 is enabled
    2011-02-10 01:47:15 [INFO] [Stats-v0.3c] Using Nijis Permissions for permissions
    2011-02-10 01:47:15 [INFO] [Stats-v0.3c] v0.3c Plugin Enabled
    2011-02-10 01:47:15 [INFO] Loaded Essentials build 172 by Zenexer, ementalo, Eris, and EggRoll
    2011-02-10 01:47:15 [INFO] [SpawnControl] version [0.5.2] loaded
    2011-02-10 01:47:15 [INFO] Minecart Mania Core version 0.79 is enabled!
    2011-02-10 01:47:15 [INFO] Persistence: Found Permissions, using it for permissions.
    2011-02-10 01:47:15 [INFO] Persistence version 0.32 is enabled
    2011-02-10 01:47:15 [INFO] WorldGuard: Permissions plugin detected! Using Permissions plugin for permissions.
    2011-02-10 01:47:15 [INFO] WorldEdit: Permissions plugin detected! Using Permissions plugin for permissions.
    2011-02-10 01:47:15 [INFO] Done! For help, type "help" or "?"
    2011-02-10 01:47:33 [INFO] CrazyMatt [/99.235.38.33:54208] logged in with entity id 119
    [B]2011-02-10 01:47:33 [WARNING] Persistence: Error updating table pluginCommands: table pluginCommands has no column named commandPermissionType[/B]
    [B]2011-02-10 01:47:33 [INFO] INSERT OR REPLACE INTO "pluginCommands" ("pluginId", "commandParent", "commandTooltip", "commandEnabled", "commandPlugin", "commandCommand", "commandPermissionType", "commandPermissionNode") VALUES (?, ?, ?, ?, ?, ?, ?, ?)[/B]
    [B]2011-02-10 01:47:33 [WARNING] Persistence: Error updating table pluginCommands: table pluginCommands has no column named commandPermissionType[/B]
    [B]2011-02-10 01:47:33 [INFO] INSERT OR REPLACE INTO "pluginCommands" ("pluginId", "commandParent", "commandTooltip", "commandEnabled", "commandPlugin", "commandCommand", "commandPermissionType", "commandPermissionNode") VALUES (?, ?, ?, ?, ?, ?, ?, ?)[/B]
    [B]2011-02-10 01:47:33 [WARNING] Persistence: Error updating table command: table command has no column named permissionType[/B]
    [B]2011-02-10 01:47:33 [INFO] INSERT OR REPLACE INTO "command" ("id", "parentId", "tooltip", "enabled", "pluginId", "command", "permissionType", "permissionNode") VALUES (?, ?, ?, ?, ?, ?, ?, ?)[/B]
    [B]2011-02-10 01:48:40 [INFO] CrazyMatt lost connection: disconnect.timeout[/B]
    [B]2011-02-10 01:48:41 [INFO] Freed 320.72698974609375 MB.[/B]

    What is at fault?
     
  19. Offline

    amkeyte


    So you've probably solved this already, but I thought I'd throw this out and show my noobliness. Since it's a singleton is there some way you could "lazy enable" in a special constructor? I have no idea how bukkit loads plugins, and now that I think about that, it's probably easier to assert some kind of priority. Of course then what do you do when another plugin also wants to be first also?

    ahh... does PluginManager have some way to enable Persistence if .getPlugin("Persistence") fails? This could be attempted before

    Code:
                log(Level.WARNING, "Persistence is required. Disabling SkinsandShirts.");
                this.getServer().getPluginManager().disablePlugin(this);
    Although enabling a plugin on the command of another plugin might break some OOP integrity. An interesting puzzle indeed.
     
  20. Offline

    NathanWolf

    If it is Persistence, you should be able to fix it up by updating to latest (just get the zip from NetherGate page) and deleting /plugins/Persistence/*.db.
    --- merged: Feb 10, 2011 4:25 AM ---
    Also, those second errors (from Persistence- the db failures) should be reasonably harmless, at least to everything but possibly NetherGate.

    I don't think they would be related to the first error you posted- that looks like a pretty hard crash, I do my best to avoid stuff like that.

    I'd make sure your CraftBukkit is fairly up to date for that one, just in case.
    --- merged: Feb 10, 2011 6:05 PM ---
    I just saw a Bukkit commit fly by that said "Commands should not be case-sensitive".

    FWIW, I've taken a slightly different approach for Persistence, and I think I'm going to stick with it.

    Commands are not case-sensitive, provided you specify them lower-case when you create the command.

    Any upper-case characters must match. This lets me do things like have a "/persist RESET" command for safety.

    Thoughts/opinion/etc, always welcome!
    --- merged: Feb 10, 2011 7:12 PM ---
    0.34 released, greatly improved phelp interface. I think it's getting very finalized, if you're using Persistence, I suggest you update and take a look to see what you /phelp tree looks like. You may want to modify your "usage" strings in particular, a bit. Here are some notes:

    - Don't preface usage with any commands- it's just meant for parameters, e.g. "<player>".
    - Leave it null if your command takes no parameters

    Following these two rules should let Persistence generate some really nice help for you.

    It doesn't page yet, so if you have a lot of commands this may get messy- otherwise, it's looking really nice IMO.
    --- merged: Feb 11, 2011 5:16 PM ---
    TODO: I just learned about BukkitScheduler. I didn't know this was implemented!

    I'm SO psyched about this- as mentioned previously, my ultimate goal is for Persistence to do incremental auto-save instead of on player join/quit...

    Well, I can do that now!

    Which also, I guess, means it'll be time for Persistence to get a properties file... I think that's how I'm going to handle it for now, anyway. I definitely want admins to be able to turn off auto save and control the rows/interval timing.

    I could just make some console commands and persist a data structure to store config data.... but ... does that seem weird to anyone?

    People seem to have some strong opinions on not storing config data in a database, I've noticed :)

    Anyway, that's just details- the important point is: Incremental. Auto. Save.

    And, yes, that will definitely mean it's time to add some log statements for things like rows saved/loaded- I think Persistence is a little too sneaky/quiet right now ;) I'll make options to turn those off, too.
    --- merged: Feb 12, 2011 4:45 PM ---
    v0.38 has got a fairly important fix in it if you're using referenced objects, such as BlockVector. @WinSock, not sure if that applies to you- let me know if so and I can go into more depth.

    The long and short of it is, I'm not sure that objects with primitive id types were loading correctly when referenced by another object. For me, so far, this mostly applies to BlockVector- most of my other DAO's have string ids, for some reason.
     
  21. Offline

    1337

    sorry to sound like a noob but how would i do this?
    Code:
    public class Data {
        Mcls m;
        private Persistence persistence = m.persistence;
    
        reload r;
        private int level;
        private int prestige;
        private int nextlvl;
        private String playername;
    
        public Data(){
        level = r.getLevel();
        prestige = r.getPrestige();
        nextlvl = r.getNextlvl();
        playername = r.getName();
    }
    how would i add or update there levels and stuff?
     
  22. Offline

    NathanWolf

    Not a noob question at all, Persistence is a little "foreign". :D

    Assuming that "Data" is the class you want save, you'd do this:

    Code:
    ... imports
    
    @PersistClass(schema="levels", name="player")
    public class Data {
    
       @PersistField
        public int level;
    
        @PersistField
        public int prestige;
    
        @PersistField
        public int nextlvl;
    
        @PersistField(id=true)
        public String playername;
    
        public Data(Player player){
        playername = player.getName();
        }
    
        public Data() {
        }
    
    }
    
    That's basically it for your data class- assuming that "reload" is something transient that you don't need to persist.

    From there, in your "main" class, you could use functions such as:

    Code:
    public boolean onPlayerSomething(Player player, String[] params)
    {
      Persistence persistence = Persistence.getInstance();
      Data playerData = persistence.get(player.getName(), Data.class);
      if (playerData == null)
      {
        playerData = new PlayerData(player);
        persistence.put(playerData); // this saves the object
      }
    
      // .. do something with playerData, put() it again if altered
    }
    
    
    I'm not too sure what the relationship is between "reload" and PlayerData- you may not need reload, depending on if it's just there due to a misunderstanding.

    EDIT: I just "cleaned up" this example, removing reload. That may or may not make it clearer, not sure :)

    This would let you persist instances of your Data class, where the id is a player name (for easy lookup and uniqueness based on players- so this is assuming this is meant to be per-player data, basically).

    I think that's about it- does that make sense?

    The basic strangeness is the Java annotations- but once your data class(es) are set up, the core API for persistence is basically just the get/put/getAll/remove functions ("putAll" is not implemented yet, or if it is, it isn't implemented correctly)

    Oh, also note that anything you want to add a @PersistField to must be declared public. You can use getters/setters instead of direct field access if you want (I would recommend it). This lets you keep your data private, but Persistence can still save/load your objects via their getters and setters. (You need both for each field, unless you add "readonly=true" to your PersistField annotation)

    And, finally- every @PersistClass class must have a default constructor. You can have other constructors and use them yourself for making objects (like I've done in the example), but just remember to have a default one, or none at all.

    Thanks for your interest! Let me know if I can be of any more help.
     
  23. Offline

    1337

    i just remembered i dont need reload anymore if i can read from persistance?
    reload was reading from a .txt file witch was how i was saving things before but this is better
     
  24. Offline

    NathanWolf

    Oh, one more note- if you wanted to, you could use a "PlayerData" class as your id instead, like this:

    Code:
    @PersistField(id=true)
    public PlayerData player;
    From a data perspective (what ends up in your db), this is identical to a "String playerName;", but at runtime you get a few nice-add-ons, such as the last time that player logged in and disconnected. And, if you're a data nerd, it maintains a semblance of referential integrity - though I never plan on doing anything crazy like setting up FK's or anything, especially since I'm using multiple schemas.
    --- merged: Feb 12, 2011 6:22 PM ---
    Sweet deal!

    Well, unless I made a blunder, that code should basically compile and save/load your data :)
     
  25. Offline

    1337

    when i use the code you posted above i get an error on the red part saying that playerdata needs to be a PlayerData not just Data
    Code:
    {
      Persistence persistence = Persistence.getInstance();
      Data playerData = persistence.get(playerData, Data.class);
      if (playerData == null)
      {
    [COLOR=rgb(255, 0, 0)]    playerData = new PlayerData(player);[/COLOR]
        persistence.put(playerData); // this saves the object
      }
    
      // .. do something with playerData, put() it again if altered
    }
     
  26. Offline

    NathanWolf

    You don't want to create a PlayerData class- that's a built-in global class managed internally by Persistence. If you're just using a String playerName id, you can ignore it altogether- I probably shouldn't have even mention it, because it just complexifies the situation for not really any good reason :) You can always refactor later, since the data will cary over.

    So, just use a String playerName to start with and ignore PlayerData. So I think your code should just be:

    Code:
    {
      Persistence persistence = Persistence.getInstance();
      Data playerData = persistence.get(playerData, Data.class);
      if (playerData == null)
      {
         playerData = new ***Data****(player);
        persistence.put(playerData); // this saves the object
      }
    
      // .. do something with playerData, put() it again if altered
    }
    Using the Data class I first gave above, ignoring the comment after it about using PlayerData as the id instead of a String.

    I put asterisks around the only part I changed (side note: I really wish the code editor allowed formatting!)
     
  27. Offline

    1337

    ok now i get an error here
    Data playerData = persistence.get(playerData, Data.class);
    it say "the local variable playerData may not have been initialized" also does put() edit existing one or make a new line each time?
    also how do you read using persistance?
     
  28. Offline

    NathanWolf

    Oops! Sorry, I knew I shouldn't have tried writing code without a compiler to check it for me...

    playerData should be player.getName():

    Code:
    public boolean onPlayerSomething(Player player, String[] params)
    {
      Persistence persistence = Persistence.getInstance();
      Data playerData = persistence.get(player.getName(), Data.class);
      if (playerData == null)
      {
        playerData = new PlayerData(player);
        persistence.put(playerData); // this saves the object
      }
    
      // .. do something with playerData, put() it again if altered
    }
    You want to look your data up based on the player's name, in other words. Sorry about that!
    The put() function enforces uniqueness based on the id- so, for each new player that comes in, a new row will be made- otherwise, it updates the existing one.

    That's essentially what I'm doing in the example code above- You're saying there "give me the instance of my Data class that matches this player id", and then "if it doesn't exist, make a new one using this player, and tell Persistence to save it".

    get() is all there is to it. Once you've got your object, just use it like a normal object, and put() again when you want it saved.

    It's really all deceptively simple :D

    At least on the exterior, it's simple- that's the idea. The minimal sample code I've given you here (assuming there are no more typos...) will actually do all of the following, with the help of Persistence:
    • Automatically create database tables and schema matching your defined persisted classes
    • Automatically load the entire table on demand (on first get() of that class type) and keep them all cached
    • Automatically track dirty objects (on put()) and save them to the database periodically
    • Maintain hashmaps for everything for easy lookup
    • Create and populate sub-tables for any List<>'s you're persisting
    • Maintain object references relationally for any persisted objects that your persisted object references
    And so on... there's a lot going on under the hood, in an attempt to make the high-level API as clean and simple as possible.

    Keep the questions coming! I'd love to get you up and running with Persistence.
     
  29. Offline

    amkeyte

    ok I've got myself caught on this one, probably doing something stupid... I'll keep working on it, but another set of eyes might be helpful. I have the latest jars of craftbukkit and persistence. While I have done pretty well with commands in persistence, the actual persistence of data is still a little tricky for me :)

    Code:
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getId is not persistable, type=java.lang.String
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWallBlockList is not persistable, type=java.util.ArrayLis
    t
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWorldName is not persistable, type=java.lang.String
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.hasWall is not persistable, type=java.lang.Boolean
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getOrigin is not persistable, type=org.bukkit.Location
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWallMaterial is not persistable, type=org.bukkit.Material
    
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getBorderSize is not persistable, type=java.lang.Integer
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWallChunkSet is not persistable, type=java.util.HashSet
    2011-02-12 20:29:39 [WARNING] Persistence: class BorderData has no fields
    2011-02-12 20:29:39 [WARNING] Persistence: Error selecting from table BorderData
    : [SQLITE_ERROR] SQL error or missing database (no such table: BorderData)
    source here:
    http://code.google.com/p/akbordergu...dbox/bukkit/akBorderGuard/dao/BorderData.java


    got any ideas? I'll update here if I figure it out.
    --- merged: Feb 13, 2011 6:05 AM ---
    I suppose this might be happening because I'm trying to "get" on a persistable that hasn't been created yet? At some points in the plugin I'll use get to check if the borderData has been populated, and if it returns as null then there is no border, therefore just return out of the function. Is this a bad idea?
    --- merged: Feb 13, 2011 7:07 AM ---
    solved most of it. I wasn't using the "readonly = true". Also it appears that using an underscore prefix for private fields trips up the mechanism. Fixed.

    Now I'll have to make persistables of the bukkit classes I'm trying to save. Sweet!
     
  30. Offline

    NathanWolf

    Hmm... well, I see one issue, but the est I can't accout for really :(

    Code:
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWallBlockList is not persistable, type=java.util.ArrayList
    
    For now, you've got to specify the type of your persisted list as List, the interface. You can still make it an ArrayList, but the type must be List for Persistence to allow it in. I may allow ArrayList (and whatever other kinds of lists) in later, it doesn't really make a difference since it's all just a List interface to me- but I have to add an individual check, or find a fancier way to use reflection than I've been able to so far.

    Really, if you're using the getters, then that's what really matters anyway, not the actual data type- so just return List in your getter and take a List in your setter, and that should work.

    But.... all of those string errors.... Yeah, Strings are definitely persistable :) So, I'm not sure what's up with that.

    Oh, also- I see a Location in there, not persistable- use a combination WorldData and BlockVector instead for a multi-world compliant location (it'd be nice to get this encapsulated into Location one day, which could happen)

    Material should be persistable, just be nature of it being an enum, but I haven't tried Materials specifically yet- I do plan to, soon.

    HashSet is also a no-go for now- my current solution (in my client code) is to persist a List, and then use it to populate a HashMap at runtime (the setter for the list is a good place to do this).

    However, I do plan on supporting directly persisting a HashMap eventually- it's no more difficult than a list of contained objects, fundamentally- it's just a matter of putting the pieces together.

    Code:
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWallBlockList is not persistable, type=java.util.ArrayLis
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getOrigin is not persistable, type=org.bukkit.Location
    2011-02-12 20:29:39 [WARNING] Persistence: Field info.aksandbox.bukkit.akBorderG
    uard.dao.BorderData.getWallChunkSet is not persistable, type=java.util.HashSet
    
    The rest of that, the stuff I cut out- Booleans and Integers and such should all be good. You can actually use the primitive types, too (int, boolean, etc)- I treat it all the same, ultimately- the only difference is with Integer, you can get a null value (if it's null in the db). If you've got a primitive type, you'll never know about null values- I'll give you back a reasonable default (zero).

    Anyway- maybe there's some sort of cascading error issue going on here? I dunno- see if you can live without the stuff I can't persist, and I'll take a look at the stuff that should be persisting.

    I'll make some time tomorrow to look this over if you haven't figured it out by then- I would suggest starting simple, make a data class with a single int or something just to prove the system out.

    I've been making very incremental changes to Persistence recently, and I do use it in a "live" plugin- so nothing so catastrophic as it completely not working should be happening :\

    I'll try and figure out what's up.

    No, that's exactly how you should use it- the classes are bound (and data is loaded) on demand, there's nothing you need to do but call get() (or put()- whichever one you call first does the binding and loading, basically).

    That's how it should be working, at least. Hopefully I can be more help tomorrow, but I've gotta get some sleep :)[/quote]
    --- merged: Feb 13, 2011 7:27 AM ---
    Heh- doh! Must hit refresh between replies :)

    Ok, well, in that long now-mostly-pointless post above, you will find some gems, one of which is a suitable replacement for Location that is persistable (BlockVector/WorldData)

    BlockVector is built-in Bukkit, but Persistence uses the programmatic API (as opposed to annotations) to make it persistable. You can actually do the same to make other classes you don't control (can't addd annotations to) persistable.

    This is pretty "high tech", in terms of how deep you want to get in the Persistence API (we're getting a bit beyond get/put here....), so it's not real well documented yet- but here's how Persistence does it internally for BlockVector, I think you'll get the basic idea:

    Code:
        // Create BlockVector class
            EntityInfo vectorInfo = new EntityInfo("global", "vector");
            FieldInfo vectorId = new FieldInfo("id");
            FieldInfo fieldX = new FieldInfo("x");
            FieldInfo fieldY = new FieldInfo("y");
            FieldInfo fieldZ = new FieldInfo("z");
    
            // Make the hash code the id, make it readonly, and override its storage name
            vectorId.setIdField(true);
            vectorId.setReadOnly(true);
    
            // Bind each field- this is a little awkward right now, due to the
            // assymmetry (lack of setBlockX type setters).
            fieldX.setGetter("getBlockX");
            fieldY.setGetter("getBlockY");
            fieldZ.setGetter("getBlockZ");
            fieldX.setSetter("setX");
            fieldY.setSetter("setY");
            fieldZ.setSetter("setZ");
    
            // Create the class definition
            PersistedClass persistVector = getPersistedClass(BlockVector.class, vectorInfo);
            persistVector.persistField("hashCode", vectorId);
    
            persistVector.persistField("x", fieldX);
            persistVector.persistField("y", fieldY);
            persistVector.persistField("z", fieldZ);
    
            persistVector.validate();
            
    This is in Persistence.updateGlobalData which does, I think, basically this right now :) There will be more of it down the road, maybe.

    Anyway, the "EntityInfo" and "FieldInfo" classes are basically replacements for the annotations- set them up and pass them to the corresponding functions, and bippity-boppity-boom, you've got yourself a new persistable class.

    This part only needs to be done once- onEnable is probably a good place. As long as the class is registered before you use it, you should be ok- since, in this case, Persistence will not be able to register it for you automatically based on the annotations.

    Glad you're making progress! Keep me posted :)
    --- merged: Feb 13, 2011 7:28 AM ---
    BTW, do you feel like you had to work around anything that should have worked?

    Ok, last thing before sleep- that last "validate()" call is not strictly speaking necessary. It's just a "sanity check and debug print" function for things like an un-contained object without an id, or a List of Lists, or crazy stupid stuff I don't support :)
     
  31. Offline

    amkeyte

    Thanks for all the help!

    For my case, I don't think I've had to work around anything, only to work out where my issues were! lol As you've noted I'm trying to use non-persistable objects, and once I ditched the underscore prefixes.. suddenly it came to life! I'm getting the hang of it now.

    For the PersistentLocation class I'm working on I'm going the tact of taking a location in a constructor, saving its internal vars as persistables with getters and setters and then adding a getLocation that will create a new instance of Location and populate it with persisted vars. It won't work for a lot objects, but this one is fairly simple. I'll report on results.

    See you tomorrow, and get some sleep! lol
     

Share This Page