Forums » Community Projects

Library Framework Discussion

12»
Feb 26, 2012 draugath link
For a while now I've been interested in create/porting a framework for the handling of reusable libraries in VO. World of Warcraft has several various frameworks already developed which would likely be relatively easy to port. In particular I've been looking at the Ace3 framework as a candidate for such a project, since it seems to be better suited to working with libraries created by multiple parties.

Part of the Ace3 paradigm is the packaging of each library that a plugin will use with the plugin. While it's certainly possible to package and distribute a stand-alone installation of Ace3, it may not always be practical. Particularly because not only would this stand-alone install need to possibly load more libraries than are currently needed, it also introduces the need for some post-plugin-loader magic at the PLUGINS_LOADED event. In the absence of a plugin loader that can handle pre-loading dependencies, this is less desirable, but I think I have a possible method of handling this approach.

In addition to packaging a version of the used libraries with the plugin, Ace3 also uses the philosophy of embedding libraries inside of the plugins that use them. Since each library is registered once (see below), a reference to the library table is then used to reduce memory useage across all plugins, and the functions of libraries that are embedded can be called as a method of the plugin itself. Of course, embedding isn't any sort of requirement, but there are obvious benefits to the support of such an approach.

Ace3 uses a small versioning library, called LibStub, to allow libraries to register and update themselves across all projects that utilize them. Libraries using LibStub should be written in such a manner that they only execute their main block of code if the library has not been registered yet. Also, they should have code which will update plugins that have already been loaded, so all plugins using this framework would end up using the latest versions of installed libraries.

Keep in mind if you decide to read up on Ace3, that a lot of the addon coding for WoW is a little more low-level. Some actions require more code to implement than here on VO. With this in mind, there are some areas that an additonal layer of abstraction may be useful to our own processes.

It may not even really be necessary to use Ace3, instead only using LibStub. However, I think that there is something to be gained from using parts of Ace3, specifically the AceAddon library.

There are some obstacles to overcome in porting any addons from WoW to VO. Namely the fact that WoW supports the use of metatables and VO does not. In most cases, this likely won't present huge roadblocks, since most of the time it appears they are making use of __index to set a default function call for a table, but there may be other areas that use other fields that are less easy to recreate.

Thoughts?

EDIT: As I don't want this to be my project, I'm currently thinking that I will probably port over the parts of LibStub and Ace3 that I think are useful to us immediately, and then setup a github or similar repo where access can be shared.
Feb 26, 2012 PaKettle link
With proper Documentation this would be a great resource!
Mar 02, 2012 meridian link
This would be a pretty useful project, even if only as a means to easily share libraries of common code across multiple plugins.

Regardless of whether an approach of standalone or prepackaging libraries within the plugin is taken, the end result should be more or less the same. I would probably favor the standalone approach myself, but one disadvantage would be that a standalone installation would have to keep legacy/outdated code for compatibility with older plugins, possibly making things more confusing for plugin authors. With the prepackaged approach, plugins that didn't make use of the legacy code simply wouldn't include it.

I also foresee some long-term possibilities that could be quite useful too. Managing the reading/writing of config settings including providing a generic GUI dialog to set the options would not only help make writing plugins easier, but would also help out plugin authors without GUI/IUP experience. I envision something like how TCS handles it's options where a plugins button could be added to the main options dialog. I've found myself using a considerable amount of common code between the vars.lua file of my own plugins lately.
Mar 02, 2012 meridian link
So I have been working on a mock-up library for providing some templates for a command line interface to help get a grasp of how best to implement things, and the way Ace does embedding multiple times to replace and old version of a library that gets loaded first with a newer one found later will be a problem for some of the things I want to do.

Basically I want the plugin author to be able to specify their own functions for some things, but if a given function hasn't been implemented in a plugin then embed a generic one to be used. This means that when embedding certain functions the library manager would first check if the plugin already has a custom function present and only embed the generic version if it does not exist. When the embedding occurs multiple times there is no way to determine whether the given function was already present in the plugin or whether it was embedded from a lower version of the library on an earlier pass.

So anyway, it would be conducive to some of the ideas I have if the embedding were to occur when the PLUGINS_LOADED event is triggered so that different versions of a library aren't embedded multiple times.
Mar 03, 2012 draugath link
Well, rather than trying to complicate things too much, if an author wanted to have a custom function, my immediate thought is to define it within the plugin at PLUGINS_LOADED and not within the library. This way if LibStub is the chosen means of managing libraries, it can update as necessary, and then only the functions that need to be custom can be rewritten. This would ensure not only the latest version is present, but also that the custom functions are in place, all without trying to create some super-complex function checking mechanism.

The other thing to keep in mind, if you want to use LibStub, is that not all libraries lend themselves well to be embedded. It might make more sense to have it create a new object. Though even a library like this should still have some way to keep track of instances that have been created. I think AceDB is an example of such a library.
Mar 03, 2012 meridian link
Okay, I see what you are saying draugath. So if a given library makes use of overriding with custom functions then it will need to be documented that the embedding should only be done after PLUGINS_LOADED, and a library that doesn't use custom functions could be embedded whenever (assuming LibStub is the first plugin loaded). Yes, I do believe that is an acceptable approach.

I skimmed through AceDB, and it looks like creating a new instance basically creates a new profile which would be applicable to multiple plugins, so indeed it wouldn't make sense to embed it in any given plugin.

What is your plan for handling the documentation? Hosting a website? I'd expect plugin authors would find that easier to use than just a read me text file or commented code in the given library.

EDIT: Perhaps having a flag set after PLUGINS_LOADED would be a good approach so that an error could be thrown if there is an attempt to embed a library with custom functions before PLUGINS_LOADED. What would be the best way to do that? Each library with custom functions could store its own flag. Would it make more sense for LibStub to flag it? Or perhaps something else... ?
Mar 03, 2012 draugath link
I think there was a misunderstanding when I was describing how I would handle your custom function approach.

Libraries, as I picture them, are a black box with an arbitrary number of interfaces for interacting with the box. You don't need to know what's inside the box, or how the box works, just how to use the various interface controls. Ideally, the library author should write the library in such a way that it's generic enough to be useable in multiple projects. To go along with that, especially in an environment with a central registry of libraries, it's expected that plugin authors that use the libraries won't modify them directly.

With that in mind, what I was actually suggesting originally is that I think it's best to use LibStub to load the library as you normally would. Let each plugin load and possibly update the library. Then the author, if they feel they need to tweak one of the functions, should rewrite what they need in the main body of their plugin and have the new version instantiated at the PLUGINS_LOADED event. This is how I would approach the situation in my own plugins.

After all that, keep in mind that LibStub is merely a portal to access the libraries. It's entirely up to the library itself to manage the updates when a new version gets loaded. The library could be written with generic functions and a method for specifying custom functions within a specific project. The idea I had to support this is similar to the way embedding is handled by most of the Ace libs. Create another table within the library, (lib.customs = {}). Possibly use an additional file with specific layout for specifying custom functions. Then when a plugin requests an instance of the lib, have it add a reference to the plugin to lib.customs along with something like {customFunc1 = true, customeFunc2 = true}. Then later if an update occurs as the mixins are being iterated over and update, just check for a match in the customs and skip it if necessary.
Mar 04, 2012 meridian link
Let me clarify a few points. I agree that a plugin author should not be modifying the code for the library itself since the same library could be used by multiple plugins. Any modifications I was talking about in my previous posts would be a case of the library modifying (adding code to) the plugins that call the library's embedding routine.

I'll provide an example of how I would use the custom functions for a library that would manage configuration settings for a plugin that I was discussing earlier. One of the things the library would do is generate a GUI dialog for the player to be able to modify the settings. A table describing the various settings used by the plugin would be provided during the embedding routine and would be used to construct the library dialog. However, that dialog would be rather simplistic, so for a case where the plugin author wanted to write their own dialog they could add the relevant code to their own plugin (following the format described by the library API).

So then when the embedding occurs, the library would check to see if an options dialog written by the plugin author already exists in the plugin code, and if it does not then generate the generic options dialog on the fly and embed it into the plugin.

I'm thinking the way that I'll handle multiple library versions running through the embedding multiple times is to have the library keep track of all the functions that have been embedded. So lib v1 would embed functions A,B & C but not D, which already existed in the plugin's code. Then lib v2 would check the list and update functions A, B & C to the latest versions and perhaps add function E which was added after v1.

That way if the plugin author were to call the library's embedding function before all the libraries have been loaded/updated it won't hose things up. It would still be beneficial to wait until PLUGINS_LOADED to do the embedding in order to save a bit of processing (especially for things like needlessly creating dialogs and then destroying them multiple times), but not necessary if there were some reason to have to do the embedding immediately.
Apr 01, 2012 draugath link
All right, it's been a long wait and I don't have much to show for it... yet. *evil laughter*

Anyway, for the moment, I've got the forked version of LibStub for VO here on pastebin.

Original LibStub documentation is available here.

A few changes were necessary due to difference between the WoW API and the VO API. Most notably is the lack of metatable support in the VO API. Also, the namespace for LibStub was changed to VOLibStub to aid in differentiation.

Since VO is lacking a plugin loader that can handle pre-loading required plugins, a copy of the latest VOLibStub should be included with every plugin/library that utilizes it. Further, i recommend that main.lua be repurposed. Rather than containing the main code for a plugin or library, it should be treated as a central loader that contains only the necessary lines of code to get everything up and running in order.

[---Example main.lua---]
-- Load Libraries
dofile('libs/VOLibStub/VOLibStub.lua')
dofile('libs/SomeLibrary-1.0/SomeLibrary-1.0.lua')
dofile('libs/AnotherLibrary-2.1/AnotherLibrary-2.1.lua')

-- Load plugin
dofile('pluginMain.lua')
[---Example end---]

Plugin authors wishing to use libraries relying on VOLibStub need to download the latest version of VOLibStub. The plugin's directory can really be structured however you want, but I recommend something similar to that illustrated in the main.lua example above. This allows all of the libraries to be compartmentalized in case the have other files they rely on, and it keeps things clean.

Library authors who wish to allow their library to be distributed as a stand-alone installation need to make sure that VOLibStub is packaged with the stand-alone package and loaded before the main code for the library is loaded. I would recommend against stand-alone distribution at this point in time for the reasons I mentioned above.

Library Fundamentals
Libraries should be written so that they can be dropped into any project and just work, assuming proper documentation. The plugin author shouldn't have to open up the code to figure out how it works. They should only need to worry about what functions it supports, what arguments the functions take, and what return vales they have. Make sure that any functions you write are checking received arguments and popping up a Lua error message if there's a problem in order to alert the plugin author to the issue.

[---Example Library---]
local lib = VOLibStub:NewLibrary("MyLibrary-1.0", 1) --returns nil if already loaded, otherwise returns a table

if (not lib) then return end --if nil, there's nothing that needs to be done

lib.somearray = lib.somearray or {}

local function NotToBeCalledDirectly()
-- do stuff
end

function lib:SomeFunction()
-- do stuff here
end

function lib:SomeOtherFunction()
-- do other stuff here
end
[---Example end---]

Embedding/Mixing In
One of the concepts that VOLibStub supports is the idea of embedding or mixing in the methods of a library into the plugin that is utilizing it so they can be called as methods of the plugin itself. To accomplish this, something similar to the following should be used.

[---Example Library embedding code---]
local mixins = {"SomeFunction", "SomeOtherFunction"} -- list of functions to be embedded

function lib:Embed(target)
for _,name in pairs(mixins) do
target[name] = lib[name]
end
lib.embeds[target] = true
end

-- Put this at the very end of the library to automatically update all functions
for target,_ in pairs(lib.embeds) do
lib:Embed(target)
end
[---Example end---]

Using a library in a plugin
In order to use a library inside of a plugin, make sure that VOLibStub and the libraries in question are loaded first, similar to the main.lua example above. To then get a reference to the library's functions use one of the following, depending upon how the library was designed to be used.

local MyLib = VOLibStub:GetLibrary("MyLibrary-1.0")

or

MyPlugin = {} -- or use declare() if you're more comfortable with that
VOLibStub:GetLibrary("MyLibrary-1.0"):Embed(MyPlugin)

----------------------------------------------------

Ending notes
Obviously, this is far from complete. I recreated some of the documentation from the original source with some slightly more VO appropriate examples, but it's basically the same thing. I did not recreate the function reference for LibStub. The functions have not changed between the version.

I have some ideas on how to implement a plugin-post-loader that could possibly handle out of sequence loading of plugins, but I admit it's not a priority for me at the moment.
Apr 01, 2012 meridian link
On taking another look at the current version of VOLibStub.lua, I notice that the lua errors thrown are written poorly. At the very least, it should be more obvious that the errors are caused by a plugin. Sure, the stack trace could be used to identify the problem stems from a plugin, but that might not be so obvious to the average end-user.
Apr 01, 2012 draugath link
Remember that the errors thrown by VOLibStub and by any libraries aren't for the average end-user typically. As for how well they are written, they mimic official lua errors in their verbage. I don't see how this could mean they are written poorly.

I didn't want to change too much from the original file, in order to make it easier to update down the road if another version came out. I will admit after generating the error examples below that some additional error checking may be necessary. Specifically Error 2 is rather confusing considering the context. Ideally, author's should only be seeing error messages you write, and not error messages from lua functions.

I went ahead and added another assertion to :NewLibrary() to handle an incorrect minor version as illustrated in Error 2, but I left the VOLibStub minor version at 1.

Here are some examples:
---Error 1--------------------------------------
plugins/VO_Toolbox/LibStub/LibStub.lua:17: Bad argument #2 to `NewLibrary' (string expected)
stack traceback:
[C]: in function 'assert'
plugins/VO_Toolbox/LibStub/LibStub.lua:17: in function 'NewLibrary'
plugins/VO_Toolbox/GUILib/GUILib.lua:3: in main chunk
(tail call): ?
plugins/VO_Toolbox/main.lua:4: in function 'f'
vo/vo.lua:511: in function 'sb'
vo/vo.lua:565: in function 'OnInterfaceReset'
vo/vo.lua:600: in function <vo/vo.lua:598>

---Error 2--------------------------------------
plugins/VO_Toolbox/LibStub/LibStub.lua:18: bad argument #1 to 'match' (string expected, got nil)
stack traceback:
[C]: in function 'match'
plugins/VO_Toolbox/LibStub/LibStub.lua:18: in function 'NewLibrary'
plugins/VO_Toolbox/GUILib/GUILib.lua:3: in main chunk
(tail call): ?
plugins/VO_Toolbox/main.lua:4: in function 'f'
vo/vo.lua:511: in function 'sb'
vo/vo.lua:565: in function 'OnInterfaceReset'
vo/vo.lua:600: in function <vo/vo.lua:598>

---Error 3--------------------------------------
plugins/VO_Toolbox/LibStub/LibStub.lua:18: Minor version must either be a number or contain a number.
stack traceback:
[C]: in function 'assert'
plugins/VO_Toolbox/LibStub/LibStub.lua:18: in function 'NewLibrary'
plugins/VO_Toolbox/GUILib/GUILib.lua:3: in main chunk
(tail call): ?
plugins/VO_Toolbox/main.lua:4: in function 'f'
vo/vo.lua:511: in function 'sb'
vo/vo.lua:565: in function 'OnInterfaceReset'
vo/vo.lua:600: in function <vo/vo.lua:598>

---Error 4--------------------------------------
plugins/VO_Toolbox/main.lua:8: Cannot find a library instance of "MyLib".
stack traceback:
[C]: in function 'error'
plugins/VO_Toolbox/LibStub/LibStub.lua:28: in function 'GetLibrary'
plugins/VO_Toolbox/main.lua:8: in function 'f'
vo/vo.lua:511: in function 'sb'
vo/vo.lua:565: in function 'OnInterfaceReset'
vo/vo.lua:600: in function <vo/vo.lua:598>
Apr 01, 2012 draugath link
While there can be a lot of discussion around what amount or style or method of error reporting a library should use, it's largely academic. If you take the very literal stance that a library is a blackbox and what is inside of it doesn't exist, then obviously when writing your library you need to keep this in mind and probably use the error() function which allows you to decide at what level of the stack the reporting should start.

error(msg, 0) => Bad argument #2 to `NewLibrary' (string expected)

error(msg, 1) => plugins/VO_Toolbox/LibStub/LibStub.lua:18: Bad argument #2 to `NewLibrary' (string expected)

error(msg, 2) => plugins/VO_Toolbox/main.lua:8: Bad argument #2 to `NewLibrary' (string expected)

error(msg, 3) => vo/vo.lua:511: Bad argument #2 to `NewLibrary' (string expected)

Level 1 is the default and appears to be the level used by assert(). The level can also go higher than 3, but that doesn't serve much purpose unless your call stack has more than 3 levels.

EDIT: The other thing that is interesting to note about the way the error message reports to the client, is that the stack trace itself doesn't change. Each of the above error messages has the stack trace shown below

stack traceback:
[C]: in function 'error'
plugins/VO_Toolbox/LibStub/LibStub.lua:18: in function 'NewLibrary'
plugins/VO_Toolbox/main.lua:8: in function 'f'
vo/vo.lua:511: in function 'sb'
vo/vo.lua:565: in function 'OnInterfaceReset'
vo/vo.lua:600: in function <vo/vo.lua:598>
Apr 01, 2012 meridian link
So the particular error message that should be a bit more descriptive is the "Cannot find library instance of <MAJOR>". That message by itself sounds like it could be a client error, and I feel it should be explicitly stated somewhere (besides the stack trace) that it is ultimately a plugin problem so the devs don't get bothered with bug reports. I suppose the simplest fix would be to change it to "Cannot find plugin library instance...".

I know you aren't expecting the average user to not ever see these errors, but that's assuming the plugin author has fully tested everything or perhaps the plugin doesn't get installed correctly/fully, etc.

And nice examples of the different error levels, btw
Apr 02, 2012 draugath link
Well, remember also that VOLibStub is supposed to be extremely simple. The only thing that should break in it, causing an error, is the author fat-fingering the registration or the request. So realistically, only the Author should ever see these messages.
Apr 15, 2012 meridian link
Could you add a LIBRARIES_LOADED event that gets generated by LibStub along with a flag to check if the loading is complete? Having dialogs get recreated each time a library gets updated with a new minor version is unacceptable, and I'm thinking this would avoid other problems where dialogs created before the loading is complete could get out-of-sync with the minor version.

The syntax would be something like this:
-- LibStub --
LibStub:PLUGINS_LOADED(event)
LibStub.isLoaded = true
ProcessEvent"LIBRARIES_LOADED"
end

RegisterEvent("PLUGINS_LOADED", LibStub)

-- Library --
local lib = VOLibStub:NewLibrary("MyLibrary-1.0", 1)
if not lib then return end

function lib:Embed(target)
if not LibStub.isLoaded then error"Embedding cannot be performed until the LIBRARIES_LOADED event" end
--continues as normal

-- Plugin --
MyPlugin = {}
VOLibStub:GetLibrary("MyLibrary-1.0"):Embed(MyPlugin) --incorrect usage, library throws error

MyPlugin:LIBRARIES_LOADED(event)
VOLibStub:GetLibrary("MyLibrary-1.0"):Embed(self) --Correct usage
end
RegisterEvent("LIBRARIES_LOADED", MyPlugin)

-- End Example --

Libraries wouldn't have to enforce only embedding after LIBRARIES_LOADED, this would just give them the option to if it is appropriate.
Apr 17, 2012 meridian link
Ugh, my head is spinning thinking of all the pitfalls and other problems that can occur with various library implementations. You had an excellent suggestion earlier in this thread, draugath, for only using the main.lua to load libraries. Doing that plus the LIBRARIES_LOADED event I describe above solves nearly all the issues I've thought of thus far.

So then the typical plugin main.lua would look something like this:
MyPlugin = {}
MyPlugin.version = '1.0'

MyPlugin:LIBRARIES_LOADED(event)
VOLibStub:GetLibrary("MyLibrary-1.0"):Embed(self)

dofile'main2.lua'
dofile'vars.lua'
dofile'ui.lua'
end
RegisterEvent("LIBRARIES_LOADED", MyPlugin)
Apr 17, 2012 draugath link
First to address your concern about recreating dialogs if the minor version changes. While it is entirely possible that a plugin author uses a then-current version of a library, and fails to update their plugin to the new version of the library when it comes out. I think this scenario may be less likely than you make it seem to be. Obviously there is something to be said for creating dialogs, and then merely creating a new dialog if an upgrade does occur, since this leaves the old dialog still resident in memory. In a library project that I started on, but which is now taking a slightly different distribution angle, I used the following.

[[--- cleanup example ---]]
local lib, oldminor = VOLibStub:NewLibrary("MyLib", 4) -- oldminor will be non-nil if an upgrade occurred
if not lib then return end -- already loaded and no upgrade necessary
if (oldminor) then lib:Cleanup() end

function lib:Cleanup()
HideDialog(fancy_dialog)
iup.Destroy(fancy_dialog)
fancy_dialog = nil
end
[[-------------------------------]]

While the extra cycles needed to recreate the dialog may be annoying to lose, how many versions of a library do you expect to find in any given set of plugins?

As for adding a LIBRARIES_LOADED event to VOLibStub itself, I think this isn't the best way to go about it. If all libraries are packaged with the plugin that is going to use them, then there is really no need for it. VOLibStub already aids in making sure that libraries aren't loaded more than they need to be. So in the end, all you'd be doing is consuming more memory with all of the event registrations, unless you remembered to also unregister them when you're done.

If some sort of delayed loading is desired, for some reason, then it should probably be handled by the plugin or library itself. I had discussed the concept of some sort of post-loader for plugins that could be used to try and identify dependencies, but it's still far from trivial to implement.
Apr 17, 2012 meridian link
Could you post more detail on how you are setting up your dialogs? I must be missing something here...

Take this example:

-- Plugin --

MyPlugin = {ui={}}
local MyLib = VOLibStub:GetLibrary("MyLibrary-1.0") --minor version 1

MyPlugin.ui.somedialog = MyLib.create_cool_dialog() --minor version 1 dialog created
MyPlugin.ui.somedialong.sometogglecontrol.value = "ON"

-- END Plugin --

So now when a new version of the library is loaded, the library either has to:
A) Do nothing, in which case you have a minor version 1 of the dialog in existance alongside minor version 2 of the library. Seems like it could be a problem if the dialog is still [EDIT: interacting, not interfacing] with the library and somehow the implementation of the library changed between revisions (haven't fully thought this one out yet). Best case scenario you still have a dialog that looks a bit different from all the dialogs instances created after the library update.
B) The library has to maintain a list of all dialog instances it has created so it can destroy them all and recreate them again with the new minor version. In this case, all the changes the plugin had made to the dialog (setting the state of the toggle to ON) are lost.

I must be planning more complicated stuff than you. I have ideas for 4 different libraries that can potentially depend on each other, so it's come to the point that they even have to be embedded in a particular order. My embedding process is a bit involved too, with lib A building on lib B, and I really don't like the idea of having to construct and deconstruct potentially multiple times.

There were several other issues that using the LIBRARIES_LOADED event fixed too, but I stopped thinking about them once I came up with that solution to them all. I'll post more once I can think of them again.

And of course, I've since run into 2 entirely different issues, but I'll save them until this discussion gets resolved first...

EDIT:
"If some sort of delayed loading is desired, for some reason, then it should probably be handled by the plugin or library itself."

That was one of my other problems. I can have the library itself register PLUGINS_LOADED so that it can prevent the player from interacting with the library (e.g. creating instances of things) until after the event occurs. The problem is if the plugin then also registers PLUGINS_LOADED to know when it can start interactions, the plugin's PLUGINS_LOADED event handler might get called before the library's event handler, in which case the library would throw an error saying to wait longer. It would be ridiculous to have each individual library generate an OK_TO_BEGIN_INTERACTIONS event.
Apr 18, 2012 draugath link
Ah, I think I see what you're doing. Though I must admit that I'm a little confused about what type of dialogs you're creating that would have embedded logic and necessitate multiple instances. The example I gave earlier was for a dialog with extensive logic behind it, but that I had created an object interface to set/get a few parameters and open it, since it didn't need to exist more than once.

This brings up a good question about when to increment the major version vs the minor version. Part of your concern is for a change in a minor revision causing a dialog created from an earlier minor to stop working or behave differently than intended.

There's a chance I'm still missing or misinterpreting some general concept, but based on my current understanding of versioning, a minor revision should be largely a bugfix release. So for instance, if you release MyLib-1.0, minor1, and it is later found that there's a problem with it, the problem gets fixed and the minor is incremented to minor2. All plugins get loaded and everything that still happens to have minor1 packaged with them get the benefit of the update. I could even see adding new features to a version so long as nothing else is touched and getting away with only incrementing the minor. Now if some fundamental part of the library's logic is changed, this would probably require a major version increment (MyLib-1.0 to MyLib-1.1).

Now to get back to your dilemma, Considering your example, Option B is most likely a non-option for multiple reasons. If we assume that a minor version increment is only going to be for bugfixes or feature additions that don't change existing code, then Option A is fine since nothing will have been impacted.

But what if it's deemed that one single default value is necessary to be changed (e.g. label font-size, toggle state, etc), should this constitute a major or minor version change? I think that decision is largely up the library author, but it still leaves us with the unanswered question of how to handle possibly different looking dialogs across multiple projects. Personally, I would probably lean towards a minor revision, so long as none of the core logic has changed, since all projects would still get the same functionality.

If the library author deems it necessary to make logic changes in a minor version that could break an older minor, then part of the library use instructions should probably require the plugin author to wait until PLUGINS_LOADED before instantiating any dialogs or objects. This should ensure that all possible upgrades have been made and the latest code is in memory, without requiring any extensive hoops be jumped through. It could further be explained that to do otherwise could introduce unexpected results down the road.

I'm not sure I really answered your question, but this is the logic I'd apply to the situation.
Apr 18, 2012 meridian link
The paradigm I've tentatively been using for major vs minor revisions is that as long as the library interface is unchanged (or at least backwards compatible), then a minor revision is acceptable. If the implementation changes, that is irrelevant so long as the same input in still gets the same output out. i.e. an internal-only library structure that was formerly an indexed table could be upgraded to a key value pair table and still count as a minor revision, as that change is transparent from any plug-in's perspective (black box).

That said, I do have concern over having multiple minor versions of a library active at the same time. Having multiple major versions of a library active simultaneously should also be avoided as much as possible, but ultimately is unavoidable, so in that case the library author has to specifically take measures to ensure that the major versions can coexist safely.

The simplest example I can think of to illustrate my point is for libraries that save data to system notes. Two major revisions saving/loading from the same notes index is obviously going to cause issues, so a major library revision also means the system notes index used will have to be incremented as well (assuming the data structure saved to system notes has changed in at least some way). If it cannot be guaranteed that only one minor library version is active at a time, then the system notes index would have to be incremented for minor revisions as well. I'm thinking it would be safe to keep the same notes index for a minor revision, provided only one active at a time is guaranteed (and SystemNotes are not available during the loading phase, so no conflicts are possible there). Also, I consider SystemNotes part of the black box provided it is invisible to plugins ...perhaps I should reconsider that position.

"...then part of the library use instructions should probably require the plugin author to wait until PLUGINS_LOADED before instantiating any dialogs or objects."

It is not sufficient to simply state the correct usage in the library API. I need a way for my library to enforce it (throw an error if an instance is created too early). As I stated earlier, it will not work to simply register PLUGINS_LOADED in the plugin (to know when to when instances can be created) and register it in the library too (to set a flag that creating instances are safe). A race condition could occur where the plugin sees PLUGINS_LOADED triggered before the library does.

What I proposed earlier with the LIBRARIES_LOADED event is essentially the same thing, except plugins would register LIBRARIES_LOADED instead of PLUGINS_LOADED, and just LibStub would register PLUGINS_LOADED instead of all the relevant libraries having to. Again, I'm not suggesting that all libraries make use of the LIBRARIES_LOADED route, just the ones that need to.

The bottom line is that VOLibStub will not work for me in its current form. If you have a better idea on how to best proceed, I am willing to hear it.