+ Reply to Thread
Page 1 of 2 1 2 LastLast
Results 1 to 15 of 29
Like Tree9Likes

Thread: Addon Developer Resources

  1. #1
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default Addon Developer Resources

    So you decided to write a Rift addon? Well that is awesome, welcome to the club!

    Hopefully this thread will shed some light on the mysteries of the dark arts of addon magic.

    Index:P.S.: Please keep the discussion in this thread to a minimum so it stays mainly a source of information. Lengthy discussions should be moved to their own thread. If you think something important is missing pr I made an error either post it here or PM me and I will gladly add/correct it.
    Last edited by Imhothar; 07-23-2012 at 10:32 PM.

  2. #2
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    The Basics
    Here you find some tips to get you started easier.

    Tomes of Wisdom
    There are many places out there where you can find information about Rift addon development. The Wiki at RiftUI has some very useful articles, especially about the members of the RiftAddon.toc file and how events and the UI frames work.

    seebs is hosting the entire addon API documentation for the Live Servers and the Public Test Server (commonly known as the PTS) in a convenient browsable format.

    As all addons are written entirely in Lua 5.1 it is a good idea to check the official Lua website (and especially the manual) if you aren't familiar with the Lua programming language. But even if you are, it doesn't hurt to have the manual bookmarked for quick reference. For Lua newcomers the Lua Programming Book is a must-read (first edition for Lua 5.0 available online, second edition for Lua 5.1 only as hardcopy, but the first edition is a very good source of information nonetheless).

    There are two example addons made by Trion showing the use of game events, frames and some Lua magic. They are a good place to start as they are quite manageable and demonstrate the basic stuff.

    Online Hosting
    Allthough it is not really necessary in the beginning, it is a big plus in the long run to create a project page at one of the online addon hosters. The two most widely known are CurseForge and RiftUI. Which one you chose is mainly up to you. Both offer an online code repository, a wiki system, ticketing, and more. Having an online presence has the big advantage of being able to share your work more easily with other developers to get coding help and support and it helps you, the author, to put down documentation, ideas, TODOs, you name it, at an easyily accessible place. Once your addon is finalized and ready to be thrown at the crowd these hosting sites give you tools to easily (even automatically) package and distribute your addon.

    Code Versioning
    Professional software developers use some sort of Code Versioning System (CVS). And we are no amateurs, are we? If the term doesn't ring a bell to you, it would be beneficial to learn about it. It makes development, versioning, backup etc of your code much easier and keeps track of all changes you ever made to your code. Both above mentioned addon hosters offer their developers free Subversion (SVN) and Git repositories. Which one you pick is up to you, but if you are completely new to CVS my personal suggestion is to use Git. The question which one is better usually results in pretty heated discussions so I won't go into details here. For newcomers there is the SVN Book and the Git Book available online. When it comes to client software it's again a matter of taste, so I will just tell you what I use: for Git I use SmartGit on Windows and Mac, for SVN I use TortoiseSVN on Windows. I deal with SVN only rarely on Mac so I just use the shell when I have to.

    The Tool
    When it comes to actual coding it is important to get a good editor. My favorite on Windows is Notepad++. I am not developing Rift addons on my Mac so I have no Lua environment set up on it. I'm sure there are devs out there who develop on a Mac and are willing to share their experience and suggestions.

    The Leage of Gentlemen
    You found your way to this forum so chances are high you skimmed some of the threads and noticed some user names reappearing regurarly. Those are usually the other authors raging about some random issues. A more interactive way of communicating is our IRC channel #riftuidev at freenode.net. Most of us hang out there most of the time chatting about addons and stuff. It's a good staging point for questions, discussions, help or just passing some time with random topics.

    The Master
    All the addons would not exist without the continuous blood-sweating efforts of over beloved evil overlord and grand master: ZorbaTHut. He's the guy who endures our rants and fullfils our wishes. So toss him a cookie here and there, I'm sure he won't mind

    back to index
    Last edited by Imhothar; 06-30-2012 at 11:48 AM.

  3. #3
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Common Lua Caveats
    Lua is a rather unique language and comes wiht its own set of "wtf?" moments when dealing with it.
    If you are not familiar with it at all I strongly suggest to dig through the Lua Programming Book first to get a general idea about Lua and how it works. Following are some points which pop up here and there and can even confuse experienced Lua programmers, as they are either not very intuitive or rarely used.

    Zero is True
    People with a background in C-like languages usually expect the numeric value 0 to be treated as false, hence if(0) fails in those languages. Lua is different. There are only two values which cause a condition to not evaluate to true and these are nil and false and no other!

    Count from 1
    Another tricky one for C-minded authors: Arrays in Lua begin at index 1. There is nothing preventing you to store values at index <= 0 (and some do this for storing additional data hidden to API functions) but all the Lua library functions as well as the Rift API expect arrays to begin at index 1 (thus ignoring values stored at indices <= 0).

    Argument passing, argument names and vararg (...)
    Lua is a very dynamic (and dynamically typed) language. Variables are not bound to the type they were created with and are allowed to change type. There is no type for each separate function signature (for example in Java foo() and foo(int) are two distinct types and not interchangeable). So in Lua, the two functions foo(a) and foo(a, b) are both just of type "function". Now you need to remember that you can call any Lua function with as many (or as few) arguments as you want. If the function expects 10 arguments and you only pass it 5 then the remaining 5 parameters are set to nil. If you call a function expecting 5 arguments with 10 then the remaining 5 arguments are simply discarded.

    There is the vararg-ellipsis ... which can hold any number of arguments. The ... ellipsis is basically just a placeholder for a list of values (value1, value2, value3, ..., valuen where n is dynamic). To find out how many arguments it holds write n = select("#", ...). Using the select command it is also possible to extract values starting from a specific index. The call select(3, a, b, c, d, e) returns c, d, e. Putting a list into parentheses extracts the first value: (select(2, a, b, c, d)) returns b only instead of b, c, d. If you need only the first few values from a list you can write a, b, c = ... to extract the first 3 values (if there are only 2 values then c = nil). A common use case of variable arguments is to forward any surplus parameters unaltered to an embedded function and frequently used for callbacks or event handler wrappers with upvalues:
    Code:
    function foo(a, b, ...)
    	-- Do something with a and b and forward all other parameters
    	bar(...)
    end
    Calling foo(1, 2, 3, 4, 5) forwards the call as bar(3, 4, 5).

    Lists and arrays can be converted between each other with the following tools:
    Code:
    function foo(...)
    	local t = { ... }
    	return unpack(t)
    end
    Putting a list between curly brackets creates a table with the values stuffed like as if you wrote { a, b, c, d }. The function unpack takes a table and returns all values from the continuous key range 1..n as a list of values. Using unpack is the only way to return a dynamic amount of values from a function (see below). Functions like print or string.format use variable arguments so they can process any amount of parameters you throw at them. Now you know how to do the same.

    Multiple return values
    A very nice feature of Lua is the possibility of returning multiple values from a function:
    Code:
    function foo()
    	return 1, 2, 3, 4
    end
    
    -- Capture only as many return values as you need
    a, b, c, d = foo()
    a, b = foo()
    a = foo()
    However, having a function return multiple values can also be problematic like in this case:
    Code:
    bar(foo())
    In such a nested function call all return values from foo are passed over to bar. But what if bar must only receive one argument? In case of a Lua function it doesn't matter as the surplus arguments are discarded (but beware: some functions accept different number of arguments to implement overloading), however Rift API methods expect to be called with the exact number of arguments as written in the specification and will throw an error if not. In that case use the same trick as mentioned in the previous section:
    Code:
    bar( (foo()) )
    By adding a pair of braces only the first return value from foo is extracted and passed to bar.

    The return values of a function can be chained if no argument is following the nested call:
    Code:
    bar("a", "b", foo())
    -- results in
    bar("a", "b", 1, 2, 3, 4)
    Is a Table Empty?
    Using the #-operator on tables which contain non-numeric keys does not work because it only returns the range of consecutive table elements starting at index 1 ignoring any values with (for example) string keys. To find out whether a table is really empty use next():
    Code:
    if next(tbl) == nil then table is empty end
    Note: always test for nil, because using not would give false positives for tables where the first element is a boolean with the value false.

    back to index
    Last edited by Imhothar; 06-30-2012 at 11:50 AM.

  4. #4
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Rift Specific
    This post is about "dos and don'ts" when it comes to addon programming. Some of it only relates to Rift addons, other is generally applicable to all Lua coding.
    It will probably change alot as the Rift API (and our understanding of it) evolves over time.

    Since 1.9 we have a bloodthirsty beast lurking around in the dark called the WATCHDOG. The WATCHDOG is tracking execution time of addons and has the power to cut of your addon if it is taking too long to finish an event handler. You should design your addon around this and make sure you split time-intensive tasks up into smaller pieces (coroutines to the rescue). To be more specific, each event handler has 0.1 seconds of execution time. If it takes longer you get a performance warning. If it takes more than 3.0 seconds the current addon's execution gets aborted and it cuts off all followinf event handlers in the queue. You have been warned. Note that using Utility.Dispatch() and triggering custom events created with Utility.Event.Create() start a new clock for the called functions/event handlers.

    What To Do
    Here you find coding idioms which are generally regarded as good practice.

    local (or: avoiding collisions) and the private table
    It is a general rule of thumb that you should declare all variables, functions, tables etc as local if they are not used outside the current scope. Because all addons share the same global environment, any variables not declared local are visible to all other addons. What is even worse: if two addons happen to name a variable the same they are going to have incompability issues and probably destroy each other.
    But what if you need a variable shared amongst multiple files? Rift automatically creates a "private" data table for every addon and it is passed to the addon source files. So you should put the following line at the top of your Lua code:
    Code:
    local addon, private = ...
    -- Rest of file goes below
    The private table (you can name it whatever you want) can be used to share data between all the files of your addon as it is always the same table. An additional bonus: The value addon.toc contains the entire RiftAddon.toc file parsed as table, allowing you to access any values from that file you need.
    If your addon has a public interface which can be used by other addons then it is good practice to create a global table with the identifier of your addon as its name and put the public methods there.

    Coroutines
    In order to avoid the WATCHDOG you need to be aware of coroutines. They are Lua's implementation of collaborative multithreading. Even though Lua runs in a single OS thread you can simulate multithreading using coroutines. The two most important functions are coroutine.create and coroutine.resume. Coroutines allow you to split up a time-consuming function into smaller pieces without having to completely redesign half of your addon. Take as example the following function:
    Code:
    local tbl = { huge table with a gazillion values }
    local function foo()
    	for i = 1, #tbl do
    		bar(tbl[i])
    	end
    end
    It isn't rocket science to note that this has the potential of freezing the client and getting the WATCHDOG into action if tbl grows in size. First we change foo to a coroutine-friendly format and create an actual coroutine right away:
    Code:
    local function foo()
    	for i = 1, #tbl do
    		bar(tbl[i])
    		if(math.mod(i, 100) == 0) then
    			coroutine.yield()
    		end
    	end
    end
    local job = coroutine.create(foo)
    coroutine.resume(job)
    Now we have the same function wrapped into a coroutine. How does it work? coroutine.create creates a new value of type "thread". The function is rigged in such a way that it effectively exits every 100 iterations, returning control to the code which called coroutine.resume. Here is the trick: when you call coroutine.resume you either start the function if it is not running already or resume from where the function was exited using coroutine.yield in exactly the same state it was left! Now you need to call coroutine.resume(job) until coroutine.status(job) returns "dead" indicating the function has terminated by finishing, not by yielding (or caused an error). Note if the function causes an error it is not automatically propagated as usual, but instead coroutine.resume returns false followed by the error message, which is suited perfectly to be placed inside assert.

    Here is an idiom you can use to split up lengthy tasks into manageable pieces almost automatically:
    Code:
    local job_coroutine
    local function job(data)
    	for k, v in pairs(data) do
    		do_something_expensive(k, v)
    		coroutine.yield()
    	end
    end
    
    local function start_job(data)
    	-- The old job is no longer required
    	-- Discard it by simply replacing the coroutine
    	job_coroutine = coroutine.create(run_job, data)
    end
    
    local function abort_job()
    	job_coroutine = nil
    end
    
    local function run_job()
    	if(job_coroutine) then
    		if(coroutine.status(job_coroutine) == "dead") then
    			abort_job()
    		else
    			assert(coroutine.resume(job_coroutine))
    		end
    	end
    end
    
    table.insert(System.Update.Begin, { run_job, "MyAddon", "run_job" }
    Now all you have to do is call start_job(...) to get your job executed asynchronously, one step per frame. Something that I have not touched here is the possibility to pass parameters to coroutine.resume and coroutine.yield to pass intermediate values the coroutine to the caller.
    Now let's talk about the WATCHDOG again. As mentioned in the beginning, it is time based. But how do you know how long each step of your job takes? Instead of yielding after a fixed amount of iterations, you can use Inspect.Time.Real to measure computation time:
    Code:
    local function job(data)
    	local yield = Inspect.Time.Real() + 0.01 -- Allow 10 miliseconds of execution
    	for k, v in pairs(data) do
    		do_something_expensive(k, v)
    		if(Inspect.Time.Real() >= yield) then
    			coroutine.yield()
    			yield = Inspect.Time.Real() + 0.01
    		end
    	end
    end
    This way you limit your coroutine steps to run at most for 10 miliseconds. Now you are way more scalable. If you are on a fast CPU more loop iterations are run before yielding. If you're on a slow CPU the coroutine adapts itself to not consume too much time at once.
    If you intend on using this solution please take a look at the Lua manual and Programming Book. They explain how coroutines work in greater detail.

    What To Avoid
    If you find code in your addon doing any of the stuff mentioned here you might think about refactoring in order to avoid bugs or "undefined behaviour". But whatever you do, never anger the WATCHDOG!

    Unsubscribing from Events using table.remove
    There is a thread explaining in detail why it is a bad idea to call table.remove on event tables. There is some argumentation whose fault it is when table.remove breaks addon event handling, and there has emerged a new suggestion here but it still needs some testing. For the time being it is strongly advised to not call table.remove on event tables. The suggested way to hook and unhook events is like this:
    Code:
    -- To hook up an event:
    local myEvent1 = { eventHandler, "MyAddon", "eventHandler" }
    table.insert(Event.Foo.Bar, myEvent)
    
    -- And to unhook:
    myEvent1[1] = function() end
    back to index
    Last edited by Imhothar; 09-25-2012 at 09:16 AM. Reason: Watchdog clarifications

  5. #5
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Tools Compilation
    Here you find a list of useful tools and libraries written by developers for developers to have a common code base for commonly required tasks and help developing new addons.
    This list is by no means complete and never will be. Adding libraries and tools one by one as they get released/suggested and tested out there in the real world.

    Tools
    LuaPad by NerfedWar
    Probably the most useful in-game utility ever. Allows you to quickly prototype and run Lua code snippets without leaving the game and to see the actual return values from API calls.

    MessageViewer by Imhothar
    I you are developing an addon that deals with addon messages this might help you prototype, develop and debug your messaging module. It shows all incoming and outgoing message traffic, various statistics and supports compression. It can also locally simulate incoming "broadast" or "send" messages from any arbitrary sources.

    StorageViewer by Imhothar
    Useful if you are developing an addon that deals with the server-side storage. It gives you the ability to view and edit the (eventually compessed) storage contents and manually upload/download and (un)compress data for testing and debugging purposes.

    Libraries
    LibAsyncTextures by Imhothar
    Helps you avoid the WATCHDOG if you need to load a lot of textures. Thexture loading is an unpredictable beast and can easily get the WATCHDOG activated. LibAsyncTextures loads textures asynchronously in the background for you.

    LibCallbackHandler by Lorandii/myrroddin
    An alternative to the built-in event structure of the Rift API. If you intend on porting a WoW addon chances are high it is using LibCallbackHandler internally, making porting to Rift easier. If you are writing an addon from scratch it's mainly a matter of taste whether you prefer Rift's event mechanism or LibCallbackHandler.

    LibCron by seebs
    Gives you the ability to register timers, recurring events and other timing-related stuff so you don't have to write all that boilerplate code again and again and again.

    LibGetOpt by seebs
    A library to ease the parsing of slash command arguments, giving you the extracted arguments packed as convenient table values.

    LibString by Imhothar
    Adds some useful string manipulation methods commonly found in other string libraries but missing in Lua. The highlight is probably the position-dependant string format which is especially useful for localization.

    back to index
    Last edited by Imhothar; 07-23-2012 at 10:23 PM.

  6. #6
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    reserved for future use

  7. #7
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    reserved for future use II

  8. #8
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Added a section about coroutines explaining how you can easily split up your time-heacy jobs into shorter pieces of execution.

  9. #9
    Plane Touched
    Join Date
    Jan 2011
    Posts
    281

    Default

    Quote Originally Posted by Imhothar View Post
    But what if you need a variable shared amongst multiple files? Rift automatically creates a "private" data table for every addon and it is passed to the addon source files. So you should put the following line at the top of your Lua code:
    Code:
    local addon, private = ...
    -- Rest of file goes below
    The private table (you can name it whatever you want) can be used to share data between all the files of your addon as it is always the same table. An additional bonus: The value addon.toc contains the entire RiftAddon.toc file parsed as table, allowing you to access any values from that file you need.
    I think I've seen this in some addons I examined, but I still don't fully understand how it works. Do I need to define a variable named "addon" for this to work?? Or is it the first defined variable??

  10. #10
    Sword of Telara Semele's Avatar
    Join Date
    Mar 2011
    Posts
    872

    Default

    Nice write up.

    I'd suggest removing the Watchdog part though as it's factually incorrect. Even if YOU make your best attempts to bind by the laws of any handler taking 0.1s it doesn't matter since any addon(s) which hit that limit before or after yours will still alert the watchdog meaning that 0.1s turns in to a fraction of the addons using that event rather than a consistent time for each addon.

    Zorba has mentioned it may be changed to something not so random, but for now it seems flawed.

    Quick example. If your handler obeys your guide and it yields @ 0.1s and avoids the watchdog, and then KBM is next in line, I'll have 0s left to work with of the watchdog timer and it'll be KBM alerting the warning not your addon, likewise if I did the same but mine was first and yielded correct @ 0.1s but yours came after, you'd be credited with the warning.

    Not a good place to be at the moment.
    Rank 76 Guardian Mage

  11. #11
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Quote Originally Posted by Sarim View Post
    I think I've seen this in some addons I examined, but I still don't fully understand how it works. Do I need to define a variable named "addon" for this to work?? Or is it the first defined variable??
    It doesn't matter what you name the variables. Every Lua file Rift loads is called with 2 arguments: the addon table and the private table (which is also available as member "data" of the addon table). Because files do not have a function header the variables are only accessible using ... at file scope which works exactly as the vararg principle described in Argument passing.
    Last edited by Imhothar; 07-02-2012 at 12:47 AM.

  12. #12
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Quote Originally Posted by Semele View Post
    Nice write up.

    I'd suggest removing the Watchdog part though as it's factually incorrect. Even if YOU make your best attempts to bind by the laws of any handler taking 0.1s it doesn't matter since any addon(s) which hit that limit before or after yours will still alert the watchdog meaning that 0.1s turns in to a fraction of the addons using that event rather than a consistent time for each addon.
    Note:
    all event handlers together have 0.1 seconds of execution time
    I will keep it there just so people know it exists. If the WATCHDOG changes I will adjust the posts accordingly. But I might add the difference between warning and killing.

  13. #13
    Plane Touched
    Join Date
    Jan 2011
    Posts
    281

    Default

    Quote Originally Posted by Imhothar View Post
    It doesn't matter what you name the variables. Every Lua file Rift loads is called with 2 arguments: the addon table and the private table (which is also available as member "data" of the addon table). Because files do not have a function header the variables are only accessible using ... at file scope which works exactly as the vararg principle described in Argument passing.
    Ahh thanks, that makes things clear.

  14. #14
    Champion
    Join Date
    Jun 2011
    Posts
    561

    Default

    Deleted casue no longer relevant

    Cheers
    N.
    Last edited by Naifu; 07-02-2012 at 03:25 AM.

  15. #15
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Added a note about error handling to the section about coroutines as pointed out by Adelea.
    Author of the Imhothar's Bags addon.

+ Reply to Thread
Page 1 of 2 1 2 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts