Wednesday, October 14, 2009

Local Globals - Are they worth it?

I've been a little.. undecided.. lately. In my pursuit to make Tweaker as efficient as possible, I've taken the approach to create local instances of globals -- for faster lookups. Lua checks the local namespace first, local to a function or block or whatever. If a variable isn't found, it starts searching up the hierarchy, eventually to the local file and, I believe, then to the global namespace. True lua might have more intermediary steps, but I think that's how things work for WoW.

By defining locally, all the global functions and tables and whatnot that I'll be using, I save Lua from having to take a step (or more) up. But when you're in an IF statement, that's inside a loop, that's inside a function -- it still needs to take those steps. How much am I really saving?

In the file's namespace, where I do my testing, it looks somewhat significant: roughly half the time. But we're still talking hundredths of a millisecond over the course of a thousand iterations.

Moreover, I have some uncertainty in how to do things. Currently, libraries (like string and table) have the library and method separated by an underscore: local string_trim = string.trim. Native lua functions, like loadstring and pairs, have their local names unchanged. local pairs = pairs. The global table is just renamed.. local g = _G. If the code calls string_join, an error is thrown if I've not set the local reference. It can be annoying getting a dozen errors, one at a time, because of this, but it's easy enough to make sure the locals are setup. loadstring and pairs are a bit harder -- I just need to sift through the code and make sure the local is defined for all these things, if I'm using them. The upside here is that intellisense works, the keywords are colored properly, because they're unchanged.

So both forms have their ups and downs. Perhaps that's why I use each. But the maintenance is getting to be a hassle. Moreover, I've realized my tests to be faulty. True enough, in the local file namespace, using the local is twice as fast. But how realistic is that? Most of your calls, especially the ones that need to be benchmarked because they're done over and over and over, are within functions, inside loops, and possibly branched with if statements. A more appropriate test shows that defining the locals in the local file provides roughly a 20% improvement in cpu usage. That's a nice number still, but a far cry from 200%, and I still wonder how nice it is when dealing with hundredths of a millisecond over thousands of runs. Is it worth it?

Python takes the approach that libraries are included in the block that uses them, whether that's the file, a function, a loop, an if statement, or any other block segment. I assume it's efficient for Python, but applying this concept to Lua locals is more hit or miss. The path still needs to be traversed in order to find the value, but now it's doing it on every function call, loop iteration, if statement, etc. and there's the overhead of the new local to store it. A single call is slightly less efficient, more calls add efficiency by comparison, but it's not likely you'll call the same function enough times to really make a gain here.

In my opinion, take it on a case by case basis. If you're calling the same function repeatedly in a block, you may want to make a local to it rather than having the global lookup again and again. If you're calling many functions from a single library in a block, make a local reference to the library: local string = string. But I'd wager that 99% of the time, it's not worth it. Stick with the normal calls. Enjoy your intellisense and your sanity!

Update: As for what's better, ("hello"):len() or string.len("hello"), I can't get any clear, consistent results. string.len tends to be slightly better more often, but the numbers jump so much I can't be definitive. However, the class.func format always works, while the obj:func format seems to only work for certain functions (myTable:getn() is nil, for example) so I'd stick with the class.func format.

Sunday, September 13, 2009

The Birth of Tweaker

I'm working with kellewic over on WoWInterface. He has some great ideas for Tweaker, and I'm loving the feedback. But it's caused me to take some steps back and refocus on what I want out of Tweaker, so I decided to share its origin with everyone. I've gotten some great early feedback in the wowi comments, and I've gotten some other feedback that I'm not sure how to respond to, but I've not really gotten much overall, good or bad, constructive or otherwise, feedback from you about what you want and what you think. So after reading this, I'd like to hear what everyone thinks about Tweaker currently and about where they'd like to see it headed.

So about a month ago, I was using Cartographer. Great addon, ckknight is vastly talented, but when you're running 200+ addons on a 3 year old machine, it gets to be a bit much. So I needed to reevaluate things. Cartographer was an area where I saw myself being able to slim down. My main interests were LookNFeel, GuildPositions, and ZoneInfo. Starting with the code for LookNFeel, I saw that it could be slimmed down greatly into its own addon that would simply tweak the WorldMapFrame with the settings that I wanted and be done. Virtually no memory footprint and no ongoing cpu usage.

After writing MapTweak to replace LookNFeel, I started working on MapZoneInfo (still a work-in-progress) to replace ZoneInfo. ZoneInfo uses numerous libraries that contain a wealth of information that, honestly, I don't care about. I cared about the level of the zone and the instances in the zone and their levels so I started tearing out the data and slimming it down to the few pieces that I wanted. Around this time, I started having some overlap... MapZoneInfo was adding information, like Cartographer does, beneath the name of the area at the top of the map. MapTweak was handling how that name should be displayed, but ZoneInfo would need to handle how this new bit of text would need to be displayed since it only existed if this addon was being ran -- MapTweak could look for it and configure it if it existed, but then what if someone used MapZoneInfo without MapTweak? There'd be no way to tweak the text. I started entering some gray area that I didn't like.

I moved on to kgPanels. I've enjoyed this addon for a long time, but there were some major flaws I was finding about it. As shown in earlier tutorials, the "border" on my panels is really just a flat, square texture. So to do this in kgPanels, I needed 2 panels. Because of the layering, the image became a third panel. Now for every panel I wanted, I needed to make 3. The list got huge. I started using the OnUpdate event to do things like positioning, because kgPanels doesn't support more than one anchor and I wanted my panels to be anchored TOPLEFT and BOTTOMRIGHT of their parent. That meant more CPU usage on top of what kgPanels was already using.. which wasn't insignificant. kgPanels doesn't have any type of consistent naming of its panels, despite supplying a name, so there was no way to reference panels. I tried events like OnLoad instead of OnUpdate, but OnLoad never fires since the widget is already loaded by the time that script is set. Problem after problem.. there had to be a better way.

Now, I don't want to condemn kgPanels. It's still a great addon. Most of these problems are simply the problems that I began to face with current addon design, which is why I thought it was time for change.

Tweaker was originally named CustomPanels, and it was designed to be a supplement to kgPanels. Like my previous addons, it had a config.lua file to create settings for it, but obviously its settings had to be a bit more robust... supporting real frames with multiple textures, fontstrings, and anchors. So as not to lack something that kgPanels had, I also included events. As I used it and tweaked it, my aspirations grew. Instead of a fixed number of settings, I allowed anything that the Widget API would allow so that the addon could grow alongside WoW, including the addition of allowing the user to create not just frames, but any type of widget. Frames were originally named CustomPanels_ where was the name supplied in order to avoid naming conflicts, but I realized that if I was able to create any type of UI element I wanted at this point, how hard would it be to tweak existing ones? So I dropped the CustomPanels_ part and allowed the config.lua file to specify the name of an existing UI element and tweak it. This is when Tweaker was truly born.

I realized now that Tweaker could do everything that MapTweak could do for the WorldMapFrame.. and more.. or less. Users could modify anything they wanted in any way that they wanted. It was no longer a simple addon, but the start of a framework. And, truthfully, it wasn't much different from just writing XML and LUA, but I felt that it was different enough, simplified enough, to continue on. And with the addition of tutorials specifically geared at Tweaker, any person could tweak their UI in common ways and even learn a little coding while they're at it.

So I've come to a crossroads now. This blog is insufficient for Tweaker, and I'm working on a website. More importantly, the simple "run your settings and be done" execution is no longer sufficient for Tweaker, in my opinion. kellewic has proposed code to help turn Tweaker into a full-fledged framework that external addons could use. And truth be told, I like it.

BUT, I don't want Tweaker to become a library. I don't want that bloat. I don't want multiple versions running around in memory. I don't want addons packaging Tweaker inside themselves in order to create their own "Tweaks" because that defeats the whole purpose of Tweaker. What that creates is what we have now.. where addons offer limited options for customizing how they look, and the more options available, the more memory intensive, cpu intensive, and bloated the addon becomes. The only difference would be that the addon authors would be using Tweaker instead of their own custom code, and that doesn't help the user. In fact, it would be more bloat for addon authors to use Tweaker for it than to do it themselves, and I trust their knowledgeable enough to do it themselves, so that's not going to happen.

What I do want is Tweaker addons... or modules. Addons that require Tweaker in order to run and use the Tweaker framework to do their stuff. This would solve a few problems and create a few nice advantages.

  • I would still support config.lua inside of Tweaker, but people would also have the option to create, for instance: MyTweaks, a custom set of Tweaker tweaks just for you and your UI. By having them separate from Tweaker itself, you no longer run the risk of losing your config.lua because I stupidly packaged my config.lua into the file when I released it. I've done it once before.. the problem was fixed before the admin got around to approving it, but there's always that risk there. More importantly, I would personally use this method for my tweaks so, fingers-crossed, I wouldn't even have a config.lua in my Tweaker folder that could accidentally get packaged up, and thus anyone using config.lua inside the Tweaker folder wouldn't have to worry about theirs getting overridden.
  • While I'm not a huge fan of UI Packages, this would also allow people to package their UI tweaks inside of a UI package. One of the problems I have with UI packages is the issue of resolution. If yours doesn't match the UI packages author, the package likely won't work for you. But one thing I did with Tweaker is make my UI fit a number of UI resolutions (not any, but anything from slightly smaller than mine to any size bigger should work because my ChatFrame and Recount stretch to fill the remaining space -- too small and Recount will squish too much though). Also, anyone familiar with Tweaker will be able to tweak the UI package further without the package author trying to supply some kind of custom addon for tweaking things.
  • My config.lua file is getting huge... this would allow me to break it up into multiple files. And thanks to this, my tutorials could be packaged up and be downloadable. People could download my tutorial for tweaking the world map and then play with it to get it looking how they want as their WorldMap_Tweaks addon. Other people that create fun tweaks to their addons could package those tweaks up and also distribute them more easily for anyone else that uses the same addon.
  • Tweaker currently exposes a bit of a global API, but that API cannot be used in certain parts of the config.lua file. Tweaker assumes that Tweaker_Data exists before it starts, so config.lua is put into memory before Tweaker.lua, thus, before the global API. This would completely solve that problem.
  • Once a tweak is made, it's done. Further modifications to the tweak can only be done using standard Wiget API calls with standard lua code. With a full-fledged framework, Tweaks could be modified at any time and then re-processed by the framework, making the necessary modifications to the widget in the same way it currently modifies any pre-existing widgets.

Of course there are downsides. More memory and CPU usage, for one. I'd make it as minimal as possible, but it would go beyond my original plans for Tweaker. However, that said, I think it's plain to see that Tweaker is no longer what it was originally intended to be, it's no longer CustomPanels. It might be time to embrace that and go all out. Tweaker is what you make of it. If you design your tweaks to be run-once-and-stop tweaks then the performance hit will be very minimal. If you need them to do something on every OnUpdate, that that's the price you pay for the functionality you're getting out of it. Performance will largely be in the hands of the user.

Please, leave me some comments. Let me know what you think. Let me know how Tweaker can best serve that thing you've been wanting to do to your UI for all this time and haven't gotten around to yet.

Saturday, September 12, 2009

Tweaker Tutorial: Templating Simple Panels

Something a little different and a little more core to Tweaker today: templates. It's an important feature that I've been meaning to get around to.

My personal UI has a number of different templates, but there are 3 that I'll demonstrate for this tutorial, and they essentially demonstrate the various qualities of templates in Tweaker.

First up is a template for your basic panel. The "base" panel in my UI has a near-black background with a dark gray, square border. It's not a "border" in the traditional sense, but just another panel, really, that's behind the background panel and sticks out over the edges to create the border. On to the code...

Virtual is a special attribute in Tweaker. There is no SetVirtual method in the Widget API, or even a Virtual method. This attribute just tells Tweaker to not render this frame -- thus making it ideal for later inheritance. XML widgets have a similar property and behavior (in fact, I think the property might have the same name, but it's been so long since I worked with XML to create objects), but the Tweaker inheritance behavior goes a bit further, as I'll show in a bit. Also, a panel doesn't need to be declared as Virtual to be inherited, but generally it will be the case.

Also, when working with virtual and inherited widgets, it's common practice to use "$parent" when naming stuff. $parent is replaced with the name of the parent widget upon creation. If this widget wasn't virtual, "$parentBorder" will be renamed as "BasePanelBorder" upon creation. As it stands, if a widget named "MyCoolWidget" inherits BasePanel, then it will get a texture named "MyCoolWidgetBorder" and "MyCoolWidgetBackground" -- this is very important. Had I named it simply "Border" then everything inheriting from BasePanel would have a texture named Border, which would in fact mean that only the first thing using it would have a texture named Border and all other widgets would simply be referencing that. If there's already a UI element named Border, then this would refer that instead of creating a texture. So be very careful to name things properly!

Strata and Level are simply what work with my UI. Adjust as necessary for yours. Points = "ALL" is another special case which tells Tweaker to call SetAllPoints().. which essentially sets TOPLEFT and BOTTOMRIGHT anchors to the parent. Since this frame is virtual, it's not defining a parent -- that'll be left up to the inheriting frames and will surely be different for each one.

The real functionality here is the Textures part. You'll notice that the "Background" texture is actually on the Border layer and the "Border" texture is on the background layer. This is because typical WoW borders are graphics that are laid over top of the background texture around the border of the object. Since I wanted a plain, flat border, it was easier to do it this way than to create an image and apply a vertex color to set the color of it. The Texture attribute defines the color of each and the Points attribute is set to "ALL" so that the background lines up with the parent widget. The border extends out by 3 units in all directions so it's nice and even on all sides.

Another type of panel in my UI is one that has a background image. This is handy for the chat so that it's not just an empty space when there's no text (and I can still read the text over the image when it's there because I've tweaked things beyond the scope of this tutorial). It's also handy for damage meters for when they're emtpy and for wherever you feel like sprucing up your UI a bit with a pretty image. You'll notice later that this is designed so that you can use ImagePanel just to add an image somewhere, it does not need to have the background color and border like the BasePanel has.

There's not much defined here. The ImagePanel is simply to add an image to something, so that's what I've defined. All my images are put on the ARTWORK layer, and they're moved inward on the widget by 1 unit so that, if there's also a background color in play, that will be visible. So far, images are always used in correlation with a BasePanel or some similar panel, so this works well for me to have that small bit of background color before jumping to the border color.

The faded image panel is specifically for panels where you're going to have something on top of the image that will need to be clearly visible. It inherits from the ImagePanel and adds a vertex color to make the image darker so it doesn't obscure things on top of it as much.

You'll see here my first use of the Template attribute. This is another special Tweaker attribute, and it actually pulls double duty. It's first duty is to find any Tweaker widget with a matching name. Template is set to only one widget here, but it can be a space-separated list of widgets to inherit from (demonstrated and discussed in more depth later). It then applies the attributes and values of that widget to the inheriting widget. In this case, it'll take everything from ImagePanel and apply it to FadedImagePanel... namely, the DrawLayer and Points attributes of the $parentImage texture.

The other duty of the Template attribute falls in line with standard Widget API. When creating a new widget, you can supply the name of a widget to inherit attributes from. If you supply to the Template attribute here the name of a widget that's not defined in Tweaker_Data, it will pass this name on to the Widget API's CreateFrame() function and the Widget API will do whatever it does to inherit stuff. I've not yet had a need to use this, so it's currently untested, and I truthfully don't know much about what it does. If you supply multiple names, Tweaker will only send the first one as I'm pretty sure the standard Widget API doesn't support multiple inheritance.

Back on to FadedImagePanel, to summarize, all this does it inherit from ImagePanel and apply a vertex color that will make it 50% darker. Had I done something like 1.0 0.0 0.0 1.0, it would make it red. The parameters are your standard color parameters: rgba -- red, green, blue, alpha. I could have gone with 0 0 0 0.5 to apply a 50% black color instead of 100% of a gray color, but I believe in my testing, that actually faded the image itself 50%.. thus showing the near-black background behind it, thus making it much, much darker than intended. 1 1 1 0.5 may have worked well, but if I ever changed my background color to red, all my images would have a red hue instead of simply looking faded. Feel free to experiment, as always.

Why bother with all this?
Like I hinted to, it makes it easy to change things globally later on. If I changed the background color of BasePanel to 1 0 0 (red), then all my panels that inherit from it would turn red. If I ever wanted to make the border thicker or thinner then I'd only need to change that in one place instead of twenty (or however many) places. It's just a convenience thing, but it's one that's imminently worthwhile, in my opinion, which is why Tweaker supports not only nested inheritance but also multiple inheritance.

Types of Inheritance: Nested
I realize I'm talking about different kinds of inheritance and you may not understand what they are, so I'll explain. Nested inheritance means that something can inherit from something (via the Template attribute) that inherits from something that inherits from something that... well, I hope you get the idea now. For example, I could create a widget that inherits from FadedImagePanel. FadedImagePanel inherits from ImagePanel. So my widget would have properties of all of these things. If you think of nested inheritance as a stack where each inherited widget is placed below the one that's inheriting it, attributes of the topmost widget take precedence over attributes of lower widgets. Or more simply, it works how you'd expect it to work (I think). If I created a widget that inherited from my FadedImagePanel widget above and it's $parentImage had VertexColor = "1.0 0.0 0.0 1.0" then the final widget being created by Tweaker would have that VertexColor instead of the one defined in FadedImagePanel.

Types of Inheritance: Multiple
Multiple inheritance works another way. Instead of stacking widgets on top of each other, I think of them as being laid out in a row, side by side, from left to right in the same order they're defined in the Template attribute, with the widget itself being the leftmost one. And then any attributes not defined by the main widget are absorbed into it through the inheriting widget. So if I created a widget (let's call it SuperWidget) that had Template = "BasePanel ImagePanel" -- my row would be: SuperWidget BasePanel ImagePanel, and I would look at all the attributes of BasePanel first and add on any not defined by SuperWidget (thus defining them for SuperWidget). Then I would look at the attributes of ImagePanel and add on any that still aren't defined by SuperWidget. So if SuperWidget defined height as 10, BasePanel defined height as 5, and ImagePanel defined it as 20, it would be ultimately defined as 10. If SuperWidget didn't define a width, BasePanel defined it as 6, and ImagePanel doesn't define it, then it's 6.

Complicated Inheritance
A more complicated use of inheritance is thus: Template = "FadedImagePanel BasePanel" -- I wish I had images to go along with this and hopefully when I get Tweaker it's own site and spruce all this up, I will, but for now, bare with me here. Now, you have something like this:

SuperWidget is going to inherit any undefined attributes from FadedImagePanel, but it's first going to inherit undefined attributes from ImagePanel for itself. When resolving an attribute for SuperWidget, the hierarchy will look like so: SuperWidget, FadedImagePanel, ImagePanel, BasePanel. Anything defined in SuperWidget itself will take precedence over anything else. The (possibly) tricky part here is that ImagePanel's attributes will take precedence over the attributes of BasePanel because they're rolled into FadedImagePanel and then FadedImagePanel is inherited before BasePanel. If you want BasePanel to take precedence over ImagePanel, it will also need to take precedence over FadedImagePanel, and you'll need to define your attribute as Template = "BasePanel FadedImagePanel"

Generally speaking, nested inheritance is when you want to make a more specific version of something. Above, I have FadedImagePanel as a more specific type of ImagePanel -- one that's faded. You might do RedImagePanel or SparklyImagePanel or whatever. You could go further and create a widget named FadedImagePanelHover, which inherits FadedImagePanel, but adds an effect so that the image is only faded when the mouse is hovering over it. Multiple inheritance is when something is different types of things, but those things are very different from each other and can stand alone. FadedImagePanel, without inheriting from ImagePanel, would not work because the DrawLayer and Points aren't set on it. ImagePanel and BasePanel do work just fine by themselves though. Panels designed for multiple inheritance generally don't define the same attributes either, as in these cases.

Now that my brain is fried trying to (hopefully) explain all this, here are some easy code examples to copy and use and play around with. The only further point I want to make here is that, with all these "virtual" widgets created and ready to be inherited from, creating panels on your existing UI elements is now easy. All you really need to do is give each one a unique name, set its template(s), and set its parent. Some elements, like the Minimap, are a bit odd with their frame strata/level and you may want to do it a little differently. If it's an image panel, you'll also need to set the path to the image and likely the TexCoord to use as well. And if you want a panel that's just a blank panel, just define the Points attribute so it doesn't inherit "ALL" from BasePanel.

Tuesday, September 8, 2009

Tweaker Tutorial: Modifying the World Map

Tweaker Version: 1.1.0+

In an earlier post on New Addon Design Concepts, a reader commented that he'd be interested in trying my MapTweak addon. I really enjoyed writing MapTweak. One might argue that it was the predecessor to Tweaker and this whole project. But as such, there's nothing that MapTweak did that can't be easily achieved in Tweaker, so that addon won't be released. Instead, I bring you this Tweaker Tutorial. (Sorry it took so long.)

For this tutorial, I found two addons to compare Tweaker's performance with: Cartographer (specifically, the LookNFeel module) and tekMapEnhancer.

Cartographer is a huge framework for creating addons that interact with the World Map. It does many more things then what I'm going to demonstrate in this tutorial, so I've removed all the other standard Cartographer modules so that we can focus purely on Cartographer itself and the LookNFeel module (which is what actually tweaks the World Map to look and behave differently). If you want those other features, you're probably best off sticking to the whole package. If you're really only concerned with making the map smaller and a bit transparent and being able to type and move and generally interact with the game while the World Map is open, then this tutorial is for you.

Tekkub, the author of tekMapEnhancer, is someone of whom I'm a big fan. She writes small, efficient addons that serve a legitimate purpose, and lots of them. That said, tekMapEnhancer is great, but it's always left me wanting more. With 7 lines of code, she's duplicated all the finer points of the LookNFeel module (which is 1352 lines long). However, tekMapEnhancer doesn't allow you to move the map, it doesn't allow you to open other UI Panels like the Character and Social panes while the map is open open, and there are no options -- you're stuck with the default 0.75 scale, the fully opaque window, the normal player arrow, and whatever else you may want to be different.

In this Tweaker tutorial, I will show you how to duplicate all the settings of LookNFeel, except overlay transparency -- I feel that it's a neat idea that only looks good half the time and requires way too much work to execute. I'll also show you how to modify how the area name and description are displayed. The description is rarely ever used by the default UI. Wintergrasp occasionally uses it, directly underneath the area name. I believe maps with nodes on them like the pvp objectives in Hellfire Peninsula may also use it.

But before we get into the tutorial, let's talk performance. As I've said before, it's important, and understanding what you're gaining from using Tweaker and these tutorials is what will make all the effort seem worthwhile.

Addon Base Startup During Configuration Map Open Map Closed
Cartographer 1702.48 KB, 110.67 ms 1723.53 KB, 1065.14 ms 1704.70 KB, 613.48 ms 1704.70 KB, 32.54 ms
LookNFeel 62.92 KB, 0.46 ms 62.92 KB, 10.85 ms 62.88 KB, 5.83 ms 62.88 KB, 3.58 ms
tekMapEnhancer 0.61 KB, 0.00 ms - 0.61 KB, 0.00 ms 0.61 KB, 0.00 ms
Tweaker 19.31 KB, 0.08 ms - 19.31 KB, 0.00 ms 19.31 KB, 0.00 ms

Keep in mind, I was polling memory and CPU usage every minute for several minutes, and Cartographer's numbers were frequently jumping up and down. The numbers above are just a close approximation of what they were most frequently at. Overall, I was seeing a steady rise in the amount of memory Cartographer was using (LookNFeel stayed steady), but it seemed to eventually plateau around 1704-1705. I can't fathom what the cause of this would be. CPU seemed to steadily decline so long as the map was closed. For both Cartographer and its module, CPU usage spiked heavily during configuration, which leads me to believe the settings were jumping through multiple hoops to eventually change the values that needed to be changed. They both had higher CPU usage while the map was open. For our purposes here, there's no need for that, but I imagine since Cartographer is a framework for the world map, there are things that it's doing and checking on behind the scenes for its other functionalities, and that's just the price you pay when using these big framework apps.

tekMapEnhancer was most impressive. The 0.61 KB seems a bit high to me, but that's still exremely minimal compared even to Tweaker. And it didn't use even as much as 1/100th of a millisecond to start up (obviously, it used some amount of CPU time, it just wasn't measurable here and thus deemed insignificant).

Tweaker, at under 20 KB, is still an impressively small addon -- especially when compared to Cartographer. It is a good bit bigger than tekMapEnhancer, but that's the price you pay for the framework (yes, like Cartographer, Tweaker is a framework for addon development, it just has a different purpose and a much lower cost associated with it). Something to quickly note, while tekMapEnhancer and Tweaker aren't consistently doing anything, they do take action when the map is opened. Excessively opening the map will use the CPU. For both addons, opening the map 20 times in a minute used about 112 ms of CPU time on my computer (very minimal, but I thought it worthwhile to mention it). If you use Tweaker to enable dragging the map, there will also be some CPU time being consumed when the map is dragged. Excessively dragging the map over the course of a minute (ie. constantly dragging and stopping and dragging and stopping and ...) used a second of CPU time. Worth noting, but generally speaking, not a concern and still significantly less than Cartographer's CPU usage (it would have this same effect on top of the CPU usage reported already).

For fun and to demonstrate the cost of the Tweaker framework, I decided to benchmark MapTweak -- it started up in 0.00 ms with a 0.76 KB memory footprint and maintained those numbers. I did this to demonstrate a point here, if you really want to squeeze the most out of your system, you'll create an addon of your own, have a lua file, and do all custom code. I find the cost of Tweaker to be minimal and worth the ease that it provides me. In this case, it's costing me roughly 19 KB of memory and 8 hundredths of a millisecond in startup time. This does not mean that Tweaker will always cost this much. As I've shown before, at its base, with no Tweaker_Data defined, Tweaker uses 0.05 KB of memory and 0.00 ms of CPU at startup. The "cost" of Tweaker comes in relation to what you're doing with it.

The Code

The Explanation
There are a number of things we're modifying here in addition to the WorldMapFrame widget. You could hook to these all individually and modify them. Since the modifications were minimal -- hiding this, scaling that -- I decided to create just a hook and make the modifications from the OnLoad event. The method you chose is up to you. Hooking to them each individually may be a bit more costly and, in my opinion, it looks bloated.

First, we hook to the WorldMapFrame. Since we know it exists, we know we're hooking to it here and not creating it. Alpha determines how transparent the frame is -- 1.0 means fully opaque while 0.0 means fully invisible. I like 0.7, it's enough to see everything clearly and still notice movement behind it. We set Movable = true so that the frame can be moved.

By default, the WorldMapFrame has the keyboard enabled. What this means is that any key you press on your keyboard sends the signal to the WorldMapFrame. This is what prevents you from chatting or moving your tune or, really, doing anything while the map is open. Very important, disable this by setting it to false.

FontStrings, this is one thing that Tweaker easily brings to the table that LookNFeel does not have an option to do. I like the values that I used above, obviously. Feel free to use any font you want and adjust any other settings that you'd like. Move them, scale them, change the color, whatever.

SecureHooks, this is new in 1.1.0. WorldMapFrame.Show is a function that's called to show the WorldMapFrame (who'd have guessed?). For whatever reason, this function sets the map's scale to 1.0 so we need to create a hook to reapply the scale that we want. That said, apply whatever scale you like within this function call. Change the 0.75 to any number you'd like. If you set it higher than 1.00, it'll be bigger than your screen. I find that 0.75 gives me plenty of room to see around the map, including my chat frame. Much smaller makes it difficult to see some things on the map, but experiment for yourself.

Next up, we have the OnLoad event. OnLoad is a fickle pickle. In order to set the OnLoad event, the widget needs to already be loaded, so it really only works when being defined in an xml file. For this reason, Tweaker will call any OnLoad event that you set once Tweaker is finished "loading" the widget. The WorldMapFrame already has an OnLoad event, but I looked around and found that it wasn't being called after load so overwriting the default one with this one doesn't really hurt anything. In the future (Tweaker 1.2.0?), I'll likely create an easy way to hook these events so you don't lose the actual code, or perhaps define an area to include code that'll execute when OnLoad is currently executing (that may be a safer approach).

Anyhow, UIPanelWindows is a collection of names of, well, Panel Windows. tekMapEnhaner keeps the world map frame in this collection, which is why you can type into chat but can't open any other Panels while the map is open (character pane, social pane, bags, etc). It may seem strange, but sometimes I like to open my bags or look at my equipment or see who's online in my guild while my map is open, so I remove the world map from the collection by setting it to nil.

UISpecialFrames is similar. It's a collection of names of frames that aren't Panel Windows but should be closed when the user presses Escape (all panel windows are closed when Escape is pressed). Since my map is no longer a panel window, it needs to be a special frame since I want it to continue to close when I press escape.

This may not be too noticeable in a 4:3 monitor, but when the world map is open, the rest of the screen is blacked out. The BlackoutWorld frame is a frame that's anchored to the WorldMapFrame and has a black texture to hide everything. Simply hide BlackoutWorld in order to remove this "feature" of the world map.

WorldMapMagnifyingGlassButton is the magnifying glass on the world map. When you click it, the map zooms out. It also has some instructional text anchored to it. I assume this is for people with only a single button on their mouse (ie. no right click). Mine has a right-click so I never use this button, so I'm hiding it.

The Player arrow on the map isn't that big to begin with. When you scale the map smaller, it gets even smaller, so I like to make it a bit bigger in order to compensate -- that's what PlayerArrowFrame:SetModelScale(1.2) does. It scales it up to 1.2 times it's normal size (20% bigger). The "Effect" frame should be the same scale since they overlap each other.

And lastly, the OnDragStart and OnDragStop events. These are what allow the map to be moved. Now that I think about it, these events are pretty simplistic and I could probably create a special property for auto generating them both. But for now, if you want the frame to be movable, just define them. If not, delete the 6 lines.

That's all! Enjoy your beautiful new map!

Monday, September 7, 2009

Benchmarking : Performance Matters

I'd like to think I'm a nice guy, although I can be blunt at times. When you're running 200 addons inside of a game that's already demanding on your system, performance matters. It matters a lot. I don't want Tweaker to compete with other addons out there. I don't want to say it's better than addons like Viewport and kgPanels. But the purpose of Tweaker is to allow the user to create a highly customized experience without all the bloat that comes with it. And numbers speak volumes. So it's with these thoughts in mind that I will be making direct comparisons of Tweaker's performance with the performance of various addons that do the same thing.

Keep in mind, Tweaker is more work to configure than your average addon. But I think it's important to get an idea of what you'll be gaining from that extra bit of work you do yourself.

To conduct these tests, I created a benchmarking addon. The code is rather simple.

Addon Base Startup During Configuration Startup After Configuration Normal Use
Viewport 489.36 KB, 2.04 ms 567.82 KB, 207.57 ms 489.36 KB, 2.44 ms 489.36 KB, 0.01 ms
Tweaker 0.08 KB, 0.00 ms - 16.48 KB, 0.03 ms 16.48 KB, 0.00 ms
Tweaker (Adding the Black Panel) - - 17.59 KB, 0.09 ms 17.59 KB, 0.00 ms
kgPanels (Adding the Black Panel) - - 146.23 KB, 2.97 ms 146.16 KB, 0.00 ms

Base startup is on a fresh, new install. During configuration is after opening the configuration windows and tweaking settings around like I would normally do -- nothing excessive, nothing minimal. Since Tweaker's configuration is done in the config.lua file outside of the game, this step doesn't apply for Tweaker. Startup After Configuration is the readings after a reloadui, and normal use is a rough average of letting the game continue to run for awhile after the UI is reloaded, without touching any configuration settings further.

Obviously there's not much to Tweaker at a base install. After doing the configuration, you'll see that the memory jumps quite a bit, but it's still significantly smaller than Viewport's memory footprint. Startup time is also noticeably faster. Since neither do any continual processing, there's no real CPU use after the initial startup.

Viewport does not include a black panel to cover up the area of the screen that the WorldFrame no longer covers. I've added a 3rd and 4th addon listing to demonstrate the memory and cpu requirements for this. You can see that adding a black panel onto Tweaker's configuration only adds another 1.11 KB of memory and 0.06 ms of startup time. Using another full-blown addon like kgPanels for this effect requires an additional 146 KB of memory and adds almost 3 ms onto your startup time.

Keep in mind, this is purely measuring performance. If I were to add more statistic here, I would say it took under 5 minutes to configure Viewport and kgPanels to get this effect. On the other hand, I spent about an hour or two figuring out how to achieve these effect on my own using Tweaker (this time is subjective based on an individual's familiarity with creating addons -- I was a bit rusty and Tweaker also required some tweaks to get it working). Fortunately, with the help of my viewport tutorial, it shouldn't take you nearly that long to achieve your own customized results.

Update: Tweaks to the Benchmarking script. The "heading" before each report is now blue so you can easily see the beginning of each report. The CPU measurement was changed to ms because it measures how many milliseconds the addon is doing something. CPU/s was changed to ms/s because it's how many milliseconds of every second the addon is doing something. The ms/s (former CPU/s) calculation was also fixed. It was previously dividing how many milliseconds were used in the last minute by the number of total seconds that had elapsed. It now divides the total number of milliseconds used by the total number of seconds elapsed. Please be aware that none of these changes affect the previous reports for Viewport since Viewport only responds to user action.. They inaccuracies were found while testing Cartographer, which does have some consistent CPU usage even while the user is doing nothing.

Thursday, September 3, 2009

Tweaker Tutorial: Modifying the Viewport

Many people change their viewport in WoW in order to give themselves some section of the screen where they can place buttons and damage meters and chat windows and whatever other blocky elements that would otherwise obstruct their view.

There have been a number of addons to do this in the past. My favorite one as of late has been Viewport. The settings are easy to work with, you can adjust things on the fly and watch the changes take effect.

However, all the ease of use and flexibility, all of those options, they cost something. At initial startup, with no settings, with no modification of the viewport, Viewport uses 40.3 KB of memory on my system. The addon itself is 325 KB of code.

To achieve the same exact effect with Tweaker uses virtually no memory and uses roughly 10 lines of code in your config.lua. Want to see it?

The WorldFrame is the UI element that renders the world. We're going to hook to that element and change it's anchors. Usually it's anchored to the TOPLEFT and BOTTOMRIGHT so it takes up the whole screen, but we'll anchor it to the TOPLEFT and TOPRIGHT so that it takes up the width of the screen (LEFT to RIGHT) and affixes itself to the top. Then I set the height to be half the width -- creating a panoramic view. This is the setting I was previously using in Viewport, and it gives me a nice bit of room at the bottom of my screen to place other things.

This may not work so well on a fullscreen monitor, but feel free to play around with the parameters all you want. You can anchor the WorldFrame wherever you'd like and adjust the height and width to whatever you'd like in order to create the viewport that's exactly right for you.

Update: It occurred to me that I forgot something important to go along with this. Starting in 3.2, I began noticing that once the viewport is modified, in certain areas, you'll get a bluish hue instead of a solid black in the "empty" space. To correct this, create a solid black panel over the area (or use an image or make a rainbow or whatever else you'd like, but I opted for a solid black panel).

Tuesday, September 1, 2009

New Addon Design

I love having options. The more, the better. But in regards to addons, options come at a price. In my addons directory right now, there are over 200 folders. There are over 30 megs of settings for my main. It takes WoW several minutes to load up, and I generally crash at least once a day. With most crashes, Blizzard recommends you clean out your WTF folder, which will reset all your addons to their initial settings. It makes sense, I can imagine those things get very unwieldy over time, but it's not something I'm willing to do. It'd be days of work to get things back to how they are.

For many addons, you configure them once and then leave them be. You position them where you want them, make them look the way you want them to look, and then they stay that way for weeks or months or possibly years. Why then are addons using all this memory to store settings that you'll likely never touch again. Some addons have their configuration screens separate, to be enabled and disabled as needed, but the setting still needs to be saved in memory. And let's face it, the Addon window from the Character Selection screen, where you specify what addons should be loaded... well, it's not very friendly. It's sort of a pain in the butt. And if you're like me, you just hit the Enable All button and let all your toons load up all the addons because it's just too much fuss doing it any other way.

With all this in mind, I've begun work on a couple of addons that take a different approach. My addons use virtually no memory, they save no variables, and the settings you create for them will not be lost if the game crashes. Each addon will search its directory for a config.lua file. The file will not be packaged with the addon because then updating the addon will mean losing your settings, but each user can create it and, with the guide of a help file, configure the addon to their own liking.

My first test of this was an addon I called MapTweak. When 3.2 hit, my Cartographer wasn't working so well. I realized my favorite features of the addon were simply the ability to make the map smaller, allow the rest of the interface to work even with the map up, and such things like that. So I deleted Cartographer and studied the LookNFeel module. I created MapTweak with a nearly identical feature set to LookNFeel, with more configuration options, and with a 0 KB memory footprint.

My next task was to add data to the various zones on the map, like Cartographer's ZoneInfo module. That module uses a library which uses a library which all accounts for a large amount of memory. Most of the data available is not even put to use. All I care to know is the level range of the zone, and the instances in it. So I got to work on another addon. No options are required here, the display settings are handled through MapTweak. It's still under development so I'm not yet sure what the memory footprint is, but I estimate it will be a couple KB at the most.

My latest and biggest endeavor so far has been to replace kgPanels. By far, this is my favorite addon for creating panels on my UI. And I've been quite happy with it. However, the memory footprint is huge, and many of the scripts don't seem to work. So I got to work on a version myself that included a config file that essentially created just a large data structure to define the properties of the panels that you wanted. Then I started adding events and scripting and making the panels responsive. Then I allowed the user to modify existing UI elements in much the same way as creating new ones (just supply the name of something that already exists). The only limit to the addon at this point is your imagination. With a little know-how and creativity, you can achieve great things. At this point, I've replaced all of my kgPanel panels, I replaced a clock addon with my own clock, I made my own area to display the zone that generally appears over the minimap, I made my own calendar button, my own mail icon to show me when I have new mail, and I've modified my viewport and gotten rid of an addon there. So far, I'm using half the memory that kgPanels used, plus saving myself memory from all the other addons that I got rid of for the other things.

Overall, this method is not for everyone. It's not user-friendly, but with proper help documentation (and tutorials for Tweaker), I hope to make it accessible to everyone that wants to squeeze a bit more out of their WoW experience. And if I should interest some people in programming and making their own addons, all the better.