Plugin Saving behavior

Posted by WillFa on Wed 04 Aug 2010 11:44 PM — 21 posts, 78,949 views.

USA #0
Plugins automatically save their state when they unload and when the world saves.

I'd suggest removing the autosave on unloading a plugin. You can close a world without saving your changes, but your plugins state is forced to be saved? What if you had an issue where a plugin parsed some data incorrectly and you have a corrupted state?

Hooking into the world's save should be sufficient. You close the world and save changes, and your plugins save. You goof up and corrupt your state, you reinstall and it's fine. At the moment, reinstalling saves the bad state and reloads it, right?
USA #1
I can't say I've really had a problem with this before. To maintain backwards compatibility, some other method of telling whether you're removing the plugin vs. closing the world would be ideal.

Alternatively, I'm not sure if this would work, but perhaps disabling the plugin then removing it would do the job without any modifications? Theoretically, the plugin is disabled, so it shouldn't execute anything in the plugin, including hooks.


[EDIT] As an extremely tangentially-related footnote, I'm considering using a state/ directory within a structured plugin containing an sqlite3 database to implement plugin state. Interesting thought. Wrap it up in a library and you can easily circumvent the normal mechanism...
Amended on Thu 05 Aug 2010 12:01 AM by Twisol
USA #2
You're not really circumventing it, since it's called from the C side of MC... It's more like reinventing it and disregarding it.


re: disabling first: I don't see why you need to jump through hoops to get plugins to behave like the world.

File -> Close
"World has changed. (Variables only) Would you like to save?"
No.
"Tough, your plugins are saving anyway."
Amended on Thu 05 Aug 2010 12:09 AM by WillFa
Australia Forum Administrator #3
WillFa said:

Hooking into the world's save should be sufficient. You close the world and save changes, and your plugins save.


Ah no, because you don't want to be saving your world every time. It really infuriates me when I load up a program and simply view a document (Word tends to do this) and then when I try to close it, it says "do you want to save your changes?". And I think, "what changes? I didn't change anything". And if you get used to saying Yes every time, sometimes you save a "bad" change (like you accidentally deleted a paragraph). Or if you get used to saying No every time, you may forget to save when you really did want to change something.

Your suggestion would have the counter-effect. Apart from annoying people and asking if they want to save their changes all the time, you then can't save the plugin without saving the world.

Say for example, your plugin keeps track of when you last levelled. Meanwhile, you change the world's colour scheme, but don't like it. You close the world and decline to save it. But with your system, the plugin's information (like the time you levelled) also would not save.

If you are a developer, and you find your plugin corrupting data, and want to save the old state file before you write over it with the corrupted version, RH double-click the plugin in the plugins list. This will open the state file, copy or rename it to something else (thus preserving it). Then close the world, and then put the old plugin state file back.

Depending on the application, I don't always agree with auto-saving. But often enough (especially in games and with non-computer people playing) it is safer to save without asking, then to force them to decide every time whether or not saving is important.

Anyway, as Twisol says, use an SQLite database instead, and you can save whenever you want to.
USA #4
Well, the plugins do act like the world. They save when you remove (read: close) them, just like the world.

A GetInfo selector or similar would probably be the easiest thing to do while maintaining backwards compatibility. A plugin that really cares can use it to save only when it wants to save. I'm still not convinced this is that useful though.
Amended on Thu 05 Aug 2010 12:20 AM by Twisol
USA #5
Nick Gammon said:

...
Say for example, your plugin keeps track of when you last levelled. Meanwhile, you change the world's colour scheme, but don't like it. You close the world and decline to save it. But with your system, the plugin's information (like the time you levelled) also would not save.
...


But as a plugin developer, in that specific instance for that type of plugin, I'd explicitly call SaveState() when you leveled. Hypothetically, let's say that your levelling up is a bunch of triggers that grabs a score/stat screen, each trigger puts values into var variables. The trigger for the last expected line calls save state. Just like you want... Except your regular expression matches for one wasn't anchored and a buddy tells you that he just leveled by cutting and pasting into a tell. Oops! That's getting saved now no matter what.

There's an older thread that Twisol wrote about being able to call SaveState if savestate="n" which you argued against.

So, Plugins save every time you think they should, or not at all, or you have to use a DB.

At the moment, the only purpose for SaveState is to put into a timer in case Automatic Updates forces your computer to reboot without saving. As an author of plugins, the only way I can control how to save my plugins is to rewrite the persistence mechanism...

My ideal scenario is along the lines of Twisol's older thread.

savestate="y" Your autosave.
savestate="n" I'll control when my plugin saves with SaveState, thanks.



p.s. Word does that because it saves the location of the cursor in the document. "I'm being helpful! You left off on page 27 of 50..."

p.p.s. cool to learn about rh dbl click. I didn't know that.
USA #6
I'll readily agree to that! I though you were talking about an application-wide removal of the automatic saving.

It should be very easy to implement. AFAIK one would just remove the code here from CPlugin::SaveState():

if (!m_bSaveState)
  return true;   // not needed


[EDIT] Actually, give CPlugin::SaveState() (but not the script-side calls) a boolean parameter. All script calls would call it with true, but when the world is closing, call it with false. The flag would be an 'override save_state' flag, so if it's true it will still execute, even if save_state is "y". Thinking out loud here...
Amended on Thu 05 Aug 2010 01:56 AM by Twisol
Australia Forum Administrator #7
I just don't agree that if the plugin is declared to not save its state, you should be able to. That is like writing to a file declared as read-only.

However in the interests of keeping scripters reasonably happy, I have added into version 4.56 another plugin callback "OnPluginWorldSave" - this gets called when the world file is being saved.

Thus you could use this to do extra things. For example, in the case of a Lua plugin, you could serialize Lua variables into the "state" variables now (rather than in "OnPluginSaveState"). That way, the variables are only "really saved" when the world file is saved.

I should warn you that unless you take extra steps, the world file won't have the "dirty" flag set just because a plugin's variables change, so the player won't be prompted to save their world file. So I really don't see what you have achieved except to potentially annoy players.
USA #8
Nick Gammon said:
I just don't agree that if the plugin is declared to not save its state, you should be able to.


It's more that it's declared to not save its state automatically, IMHO.

[EDIT] For posterity, the original save_state thread is here: http://www.gammon.com.au/forum/bbshowpost.php?id=10094
Amended on Thu 05 Aug 2010 04:44 AM by Twisol
USA #9
At any rate, this should also do it:

do
  local old_ss = SaveState
  function SaveState()
    __SAVE_STATE__ = true
    old_ss()
    __SAVE_STATE__ = false
  end
end

function OnPluginSaveState()
  if not __SAVE_STATE__ then return end
  
  -- state saving stuff
end


Just make sure you have save_state="y" or SaveState() still won't do anything.
USA #10
Imagine a plugin that you need to configure some ansi settings for, or grabs them per player in a setup routine.

You'd want to save this configuration.

Partying/grouping is ansi yellow, okay, remember that.

You're currently partying with Tom, Dick and Harry.
Okay, this transient state doesn't really need to be remembered for later sessions, but, you want to do something like set a trigger for @target hits (!@partymembers) and defend them

You want to use MC variables for the variable expansion (instead of coding for adding and deleting groups of triggers...) but you don't want to worry about these values hanging around the next time you log in...

It's easier to call SaveState after your config routine than deal with code for "I want to remember this, I don't need to remember this, I need to make sure that this is current info, Is this stale?".


I agree with Twisol's automatically comment. Perhaps adding another xml attribute defaulting to
autosave="y"
Would not break backwards compatibility, and give those that want to code plugins that need to save configuration, but not complete state a way to shut off autosaving when you close the world.


Oh, speaking of read only stuff... It's weird that if you have a <variable> defined in the plugin, changing its state and saving/reloading it gives an xml warning. Cuz, ya know, it's strange that YMMVBYVMN (Your mileage may vary, but your variables may not). :)
USA #11
WillFa said:

I agree with Twisol's automatically comment. Perhaps adding another xml attribute defaulting to
autosave="y"
Would not break backwards compatibility, and give those that want to code plugins that need to save configuration, but not complete state a way to shut off autosaving when you close the world.

Changing save_state="n" this way wouldn't break backwards compatibility either, because you're simply not supposed to call SaveState() with save_state="n" anyways. This change would only allow new things, not block old ones.


WillFa said:
Oh, speaking of read only stuff... It's weird that if you have a <variable> defined in the plugin, changing its state and saving/reloading it gives an xml warning. Cuz, ya know, it's strange that YMMVBYVMN (Your mileage may vary, but your variables may not). :)

Welcome to Quirks Mode.
USA #12
Yea, I know it wouldn't break backwards compatibility, but adding the second attribute lets Nick remain happy about what save_state is named. ;)

save_state="nah, don't bother" save_state="yes, please"
vs.
save_state="no, it is forbidden!" save state="you may."
Amended on Thu 05 Aug 2010 05:54 AM by WillFa
Australia Forum Administrator #13
Twisol said:

At any rate, this should also do it:

do
  local old_ss = SaveState
  function SaveState()
    __SAVE_STATE__ = true
    old_ss()
    __SAVE_STATE__ = false
  end
end

function OnPluginSaveState()
  if not __SAVE_STATE__ then return end
  
  -- state saving stuff
end


Just make sure you have save_state="y" or SaveState() still won't do anything.


Ah, no. When MUSHclient saves state, it doesn't redirect back through the Lua glue routines (or the Lua script space).
Australia Forum Administrator #14
WillFa said:

You want to use MC variables for the variable expansion (instead of coding for adding and deleting groups of triggers...) but you don't want to worry about these values hanging around the next time you log in...


Delete those variables when the plugin is installed - or the world is connected, or whenever is relevant?
USA #15
Nick Gammon said:

Twisol said:

At any rate, this should also do it:

do
  local old_ss = SaveState
  function SaveState()
    __SAVE_STATE__ = true
    old_ss()
    __SAVE_STATE__ = false
  end
end

function OnPluginSaveState()
  if not __SAVE_STATE__ then return end
  
  -- state saving stuff
end


Just make sure you have save_state="y" or SaveState() still won't do anything.


Ah, no. When MUSHclient saves state, it doesn't redirect back through the Lua glue routines (or the Lua script space).


You're saying that SaveState() doesn't force OnPluginSaveState to be called??
Australia Forum Administrator #16
Unless I have misread your intention here, you are replacing the SaveState function. I am saying that the replaced function won't be called when MUSHclient saves the plugin state.

For example, if you replace the ColourNote function, that will affect ColourNote calls in your script, but not if MUSHclient chooses to do a ColourNote itself (eg. on a script error).
USA #17
Yeah, but SaveState() isn't called by MUSHclient ever anyways, because it's provided for the user to call. My little hack here provides OnPluginSaveState code a way to tell whether it was invoked from a scripted SaveState() call, or a plugin-removed/world-closed event. When you call this SaveState(), it sets a flag. This way you can only respond to SaveState() calls, and not the closing events.
Australia Forum Administrator #18
Oh, OK. It is like a "note to self" that you are doing a manual save state?
USA #19
Yeah, precisely.
Australia Forum Administrator #20
Oh well, I amended it to allow a call to SaveState to save the state even if save_state is not enabled. Personally I think it will cause hassles, but whatever. It should be backwards compatible, excepting possibly those plugins that manually save state without the flag set (at present) will now actually get the state file next time.