Having already received permission to port LDB to Rift, I finally had some time to look at LibDataBroker-1.1's code, and I have some questions. Because Rift self-checks addon versions in the toc, there is no need for LibStub, and CallbackHandler-1.0 has been replaced by the Utility events, specifically Utility.Event.Create. That makes some things easier.
However, in the code, it checks oldminor, and in the current version, oldminor = 4. It then goes on to effectively say if oldminor < 4 (which is never true, as I read it) then do stuff. There are no else statements, but it also says if oldminor < 3, 2, 1 then do stuff. None of that is true either, if oldminor = 4.
Um, what? How does this even do anything? Would someone please explain? I can even make this simpler: assume the latest version of LDB is the first version, and there were no prior versions, what can be stripped out because I do not need legacy compatibility?
A couple of other questions. I keep looking at line 6, which psuedo translates to 4 = 4 or 0, or true = true or false. I am not sure how or why that makes the rest of the code work.
Also, what makes LDB necessarily hard-embedded rather than optional dependent?
local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4)
Iirc, in the case that no version of LDB has been loaded, yet, this will return an empty table for lib and nil for oldminor. Thus line 6 will be: oldminor = nil or 0 = 0
In the case an older version of the lib has already been loaded by another addon, the call will return that lib's table and the old minor version eg 3. Line 6 would then be: oldminor = 3 or 0 = 3
Also that's where the other ifs come into effect: updating an older version of the lib without breaking already registered stuff.
Hmm this is oddly more difficult that I originally thought.
ZorbaTHut's LibUnitChange code, with an example of how to use Utility.Event.Create.
My WIP conversion code, which at this point isn't much more than commenting out stuff.
I am looking at the Utility.x documentation, and can see where it might be used to convert the CBH stuff, as what needs to be done is convert the callbacks into table return values. I am not sure if/why I need to convert lines 24-27 given that they are variants of a theme, or sum them up into one table.
Not sure if this is useful in context, but in Rift, there is Utility.Dispatch which you put into AddonA, but makes the system think AddonB is doing the work of the function. Error reports will point to AddonB.
Line 47 seems easier to convert, but then I stopped the whole process and thought about this Identifier thing. To catch you up, each Rift addon has a unique Identier string in its RiftAddon.toc, (linked because of the slight, but important differences) but since I have no idea what the Identifier for any given addon that uses LDB would be, and there is no addon APIs to get that info in game currently, I looked at Zorba's code to see what he's doing.
Naturally, he works around the problem of the unknown Identifier by having addons register with his lib, gmatching, and some other things.
I don't really want to ask for someone else to rewrite this, as I am trying to learn how to do the conversion, nor do I want to reinvent the wheel by rewriting from scratch. I'd just as soon use the existing LDB code as much as possible.
Here is the short version of my questions:
With no LS/CBH, do I need oldminor, and if so, what should I set it to?
Converting the LibDatabroker_AttributeChanged callback: there are differences in the 4 methods, how do I slim those to one table return value?
What suggestions do people have to overcome this Identifier thing? Come up with a Register function like Zorba did?
The oldminor comparison is for incremental upgrades, i.e. if the version 2 was loaded, it will only execute the code to upgrade from version 2 to version 3 then version 4. For the sake of simplicity you can "skip" these incremental upgrades (e.g. remove the tests but keep the code).
Also remember that LibStub was intended to embed libraries into several addons and yet use the same instance. If an old addon loaded an old version of a library, another addon loaded afterwards with a newer version would upgrade it. That is the whole point of LibStub and the oldMinor return value : if the current minor if greater or equal to the one you declare, it returns nil, indicating there is no need to upgrade.
Edit: looking at Rift doc, you probably don't want to handle any of this, provided you could tell AddonA to embed and load "AddonA/LibDataBroker/RiftAddon.toc" and AddonB to embed and load "AddonB/LibDataBroker/RiftAddon.toc" and the Rift dependency system loaded them in the right order.
Alright, I removed the reference to oldminor, because Rift performs the version check; there is no need for minor increments. If two or more copies of LDB exist in the addons folder, or included with other addons, the game checks the Version string and loads the newest automatically, discarding the oldest from memory. Essentially, Rift performs what LibStub was designed for.
I then kept everything else, still with the callbacks commented out until I figure out how to convert that code to Utility.Event.Create. Working on it.
I ported LibDataBroker-1.1 to Rift as LibDataBroker-1.0. This is 100% UNTESTED, and I fully expect bugs.
The biggest challenge was converting the callbacks to Utility.Event.Create, and I would be thankful if someone tested, or looked at my code to see if I got it even remotely correct.
The Rift events are Event.LibDataBroker.AttributeChanged and Event.LibDataBroker.ObjectCreated
Let me know, good or bad. And especially if you have fixes. This was a good way to pull hair out!
I'm pretty sure that :
- the file should be named "LibDataBroker-1.0.lua" and not only "LibDataBroker-1.0",
- you should call Utility.Event.Create only once instead of calling it each time you have to trigger the event, something along these lines :
lib.FireAttributeChanged = lib.FireAttributeChanged or Utility.Event.Create("LibDataBroker", "AttributeChanged")
lib.domt.__newindex = function(self, key, value)
-- ...
lib.FireAttributeChanged(name, key, value, self) -- dot, not colon
-- ...
end
Why should the file be called "LibDataNroker-1.0.lua" instead of "LibDataBroker-1.0.lua"? I adjusted the code as per your suggestion, which makes sense and reduces bloat.
Further, but it is still the conversion thing that's bothering me. The signature/syntax of Utility.Event.Create has two return values, but it appears I am only using one. There is both a handle and an eventTable; using the method suggested, does this return the eventTable?
Further, but it is still the conversion thing that's bothering me. The signature/syntax of Utility.Event.Create has two return values, but it appears I am only using one. There is both a handle and an eventTable; using the method suggested, does this return the eventTable?
I don't understand what is the eventTable returned by Utility.Event.Create. The documentation is pretty unclear about it. You should probably ask people on the RiftUI forum.
Dusting this off, and this time, I have more general Lua questions. With help from the official Rift forums, I have figured out the syntax for Utility.Event.Create, but I have one question regarding functions.
Take this paste for example. It is correct up until line 21, whereupon my general questions begin.
How do I refer to a specific function as a string or table? Just passing 'self' isn't going to cut it.
Same question for this code chunk. The reason it is important is because of the two blah, blah2 = Utility.Event.Create() ... blah is the function with passed parameters I want to handle the created event. blah2 is the parameters passed to the event, and is not a problem, because it is correct.
For clarity, in the second paste, blah is LibDataBroker:NewDataObject(name, dataobj, identifier) and blah2 is dataobj. If I just refer to 'self' for blah, then it gets LibDataBroker, and not the specific function with passed parameters.
... So i looked up RiftUI's API and this particular API.
It is really, really inefficient way of doing things. Im not sure you could transpose LDB over to rift. You might be better off copying CBH over and just using that, it would be easier.
Im not sure you could transpose LDB over to rift. You might be better off copying CBH over and just using that, it would be easier.
I did a basic cut & paste port of CBH, but I'll have to look at it again to see if it needs adjusting. You are correct, it would make things so much easier if CBH was working.
Inefficient how? Is it because of how Rift registers events as tables?
It's just that it's not a clean and clear way of doing things. IMO (Bias) even if they up and copied the classic Ace2 event handler and used that instead it would be an easier way of handling things. Maybe it was my lack of looking but i didn't find a clear way of registering for an event on the RiftUI wiki..
RiftUI events in a nutshell (according to the wiki):
-- Create a new event ("MyAddon" is used for debugging, "SubCategory.MyEvent" is the actual event name and should be unique across all the event system) :
local eventFireHandler, eventTable = Utility.Event.Create("MyAddon", "Event.SubCategory.MyEvent")
-- Register an event handler:
local function myhandler(param1, param2, param3)
-- ...
end
-- "MyAddon" and "MyHandlerLabel" are just used for debugging
tinsert(Event.SubCategory.MyEvent, { myHandler, "MyAddon", "MyHandlerLabel" })
-- Fire an event
eventFireHandle(param1, param2, param3)
The system is made so that all events should declared beforehands. You cannot use an arbitrary event name and you cannot register events that have not been created ; two things you can do with CBH. This requires that the load order should be honored as for variables and functions : if addon B would use an event fired by addon A, addon B should wait for addon A to load before registering its handler.
For LDB, I think you should only declare two events, one for object creation and another one for attribute changes. Give up with the per-object and per-attribute events.
RiftUI events in a nutshell (according to the wiki):
...
The system is made so that all events should declared beforehands. You cannot use an arbitrary event name and you cannot register events that have not been created ; two things you can do with CBH. This requires that the load order should be honored as for variables and functions : if addon B would use an event fired by addon A, addon B should wait for addon A to load before registering its handler.
That is Ugly way of doing things..
@myrroddin how bout a Better Event hander for RiftUI ( Granted Untested but should work :)
local CBH = LibStub("CallbackHandler-1.0")
local eventFrame = {}
eventFrame.cbh = CBH:New(eventFrame, "RegisterEvent", "UnregisterEvent", nil)
local findTable = function(...)
local t
for i = 1, select("#", ...) do
local e = select(i, ...)
if i = 1 then t = _G[e] else t = t[e] end
if not t then return error("Bad Event Name "..string.join(".", ...) ) end
end
return t
end
--Due to the way the callback works, the event has to be registered with "_" notation, so our OnUsed
--function handles the translation to "." doted notation and back
function eventFrame.OnUsed(registry, addon, event)
local eventTable = findTable(event:gsub("_", "%.") )
tinsert(eventTable, { function(...) eventFrame.cbh:Fire(event, ...) end, "MyAddon", "SomeLabel")
end
eventFrame:RegisterEvent("Some.Event")
function eventFrame:Some_Event(event, ...)
end
With a better Event handler like this, you can write the addon like it's in wow, where events don't care that much :)
However, in the code, it checks oldminor, and in the current version, oldminor = 4. It then goes on to effectively say if oldminor < 4 (which is never true, as I read it) then do stuff. There are no else statements, but it also says if oldminor < 3, 2, 1 then do stuff. None of that is true either, if oldminor = 4.
Um, what? How does this even do anything? Would someone please explain? I can even make this simpler: assume the latest version of LDB is the first version, and there were no prior versions, what can be stripped out because I do not need legacy compatibility?
Also, what makes LDB necessarily hard-embedded rather than optional dependent?
Iirc, in the case that no version of LDB has been loaded, yet, this will return an empty table for lib and nil for oldminor. Thus line 6 will be: oldminor = nil or 0 = 0
In the case an older version of the lib has already been loaded by another addon, the call will return that lib's table and the old minor version eg 3. Line 6 would then be: oldminor = 3 or 0 = 3
Also that's where the other ifs come into effect: updating an older version of the lib without breaking already registered stuff.
Not sure if this is useful in context, but in Rift, there is Utility.Dispatch which you put into AddonA, but makes the system think AddonB is doing the work of the function. Error reports will point to AddonB.
Line 47 seems easier to convert, but then I stopped the whole process and thought about this Identifier thing. To catch you up, each Rift addon has a unique Identier string in its RiftAddon.toc, (linked because of the slight, but important differences) but since I have no idea what the Identifier for any given addon that uses LDB would be, and there is no addon APIs to get that info in game currently, I looked at Zorba's code to see what he's doing.
Naturally, he works around the problem of the unknown Identifier by having addons register with his lib, gmatching, and some other things.
I don't really want to ask for someone else to rewrite this, as I am trying to learn how to do the conversion, nor do I want to reinvent the wheel by rewriting from scratch. I'd just as soon use the existing LDB code as much as possible.
Here is the short version of my questions:
Also remember that LibStub was intended to embed libraries into several addons and yet use the same instance. If an old addon loaded an old version of a library, another addon loaded afterwards with a newer version would upgrade it. That is the whole point of LibStub and the oldMinor return value : if the current minor if greater or equal to the one you declare, it returns nil, indicating there is no need to upgrade.
Edit: looking at Rift doc, you probably don't want to handle any of this, provided you could tell AddonA to embed and load "AddonA/LibDataBroker/RiftAddon.toc" and AddonB to embed and load "AddonB/LibDataBroker/RiftAddon.toc" and the Rift dependency system loaded them in the right order.
I then kept everything else, still with the callbacks commented out until I figure out how to convert that code to Utility.Event.Create. Working on it.
I'm not sure about the attribute callbacks though, as it seems you can't use any strings as event name ; you have to declare them beforehands.
I ported LibDataBroker-1.1 to Rift as LibDataBroker-1.0. This is 100% UNTESTED, and I fully expect bugs.
The biggest challenge was converting the callbacks to Utility.Event.Create, and I would be thankful if someone tested, or looked at my code to see if I got it even remotely correct.
The Rift events are Event.LibDataBroker.AttributeChanged and Event.LibDataBroker.ObjectCreated
Let me know, good or bad. And especially if you have fixes. This was a good way to pull hair out!
If all goes well, I will release the addon.
- the file should be named "LibDataBroker-1.0.lua" and not only "LibDataBroker-1.0",
- you should call Utility.Event.Create only once instead of calling it each time you have to trigger the event, something along these lines :
Further, but it is still the conversion thing that's bothering me. The signature/syntax of Utility.Event.Create has two return values, but it appears I am only using one. There is both a handle and an eventTable; using the method suggested, does this return the eventTable?
He was pointing out that it has no .lua extension in the zip file you posted.
D'oh. /headdesk #LFMF
I don't understand what is the eventTable returned by Utility.Event.Create. The documentation is pretty unclear about it. You should probably ask people on the RiftUI forum.
Take this paste for example. It is correct up until line 21, whereupon my general questions begin.
How do I refer to a specific function as a string or table? Just passing 'self' isn't going to cut it.
Same question for this code chunk. The reason it is important is because of the two blah, blah2 = Utility.Event.Create() ... blah is the function with passed parameters I want to handle the created event. blah2 is the parameters passed to the event, and is not a problem, because it is correct.
For clarity, in the second paste, blah is LibDataBroker:NewDataObject(name, dataobj, identifier) and blah2 is dataobj. If I just refer to 'self' for blah, then it gets LibDataBroker, and not the specific function with passed parameters.
It is really, really inefficient way of doing things. Im not sure you could transpose LDB over to rift. You might be better off copying CBH over and just using that, it would be easier.
It's just that it's not a clean and clear way of doing things. IMO (Bias) even if they up and copied the classic Ace2 event handler and used that instead it would be an easier way of handling things. Maybe it was my lack of looking but i didn't find a clear way of registering for an event on the RiftUI wiki..
The system is made so that all events should declared beforehands. You cannot use an arbitrary event name and you cannot register events that have not been created ; two things you can do with CBH. This requires that the load order should be honored as for variables and functions : if addon B would use an event fired by addon A, addon B should wait for addon A to load before registering its handler.
For LDB, I think you should only declare two events, one for object creation and another one for attribute changes. Give up with the per-object and per-attribute events.
Or as OrionShock said, use CBH.
That is Ugly way of doing things..
@myrroddin how bout a Better Event hander for RiftUI ( Granted Untested but should work :)
With a better Event handler like this, you can write the addon like it's in wow, where events don't care that much :)