View Single Post
Old 03-09-2005, 01:32 PM   #8
lcalabrese
A Hill Giant
 
Join Date: Jan 2005
Server: The Seventh Hammer
Posts: 35
Interface Author - Click to view interfaces
Lightbulb "Clearing up the Muddy Waters" or "How to Fix SOE's Customization Implementation"

I think you guys misunderstood what Angahran was initially talking about when he mentioned "overloading". For those of us unfamiliar with the term, it means being able to redefine something in the same namespace using the same name but with different functionality. But in this sense, what Angahran really meant was something halfway between overloading and overriding, with a leaning toward overriding. But enough semantics, here's a more concrete example of what he meant (it's long, but I think it covers all the bases that need to be covered):

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

This post will do the following:
- First, provide a concrete example of what Angahran was talking about
- Give three different approaches to accomplishing the task set out in the example and analyze them
- Provide an explanation of the XML object model
- Explain what we want to be able to do, and a few ways of how to actually do this
- Finally, respond to a few dangling ends in this thread

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

Let's assume for the moment that you want to change the look of every gauge in the game. You can't just edit the window pieces graphic because what you want to do is add a second frame of animation to every gauge fill to sparkle every 8000ms. It's probably not the best example, but it's the best I can come up with right now, so bear with me here (it's this kind of global change that we're after in the example)...

So, naturally you're going to want to modify the animation A_GaugeFill that is found in the default EQUI_Animations.xml file. You'll also want to add another TextureInfo tag, but that's not too big of a problem. SOE's current implementation leaves you with a couple options, none of them good in my opinion:

1) Take all 198 KB of EQUI_Animations.xml and copy it into your custom directory. Then modify A_GaugeFill to add the animation frame, and add the TextureInfo.

---> What's wrong with this? Well, A_GaugeFill takes up probably about 250 bytes. That means there's 197.75 KB of definitions in your custom UI folder that shouldn't be. Heaven forbid SOE ever decides to change one of these or (gasp) add a new animation to the game. You'll never see it. Hope it's not necessary for that new offline bazaar window to work properly!

2) Leave EQUI_Animations.xml alone, because it's too big and there's too big of a potential for something to go wrong. Instead, take every xml file for every window with a gauge in it, and into each, add a new (correctly namespaced) gauge Ui2DAnimation and you're gonna have to have n (where n is the number of windows with a gauge in it) copies of the new graphics file so you can make sure the TextureInfo is loaded properly. So in every window, we'll have a different TextureInfo definition too. Then we change the <GaugeFill> tag in each gauge to point to the correct Ui2DAnimation.

---> Why do we need to put the TextureInfo in so many different files? We cannot be completely certain that SOE will keep the order files are loaded in static. Even though the character creation window is loaded first now, that doesn't mean the next patch won't completely change that. Since it's not our code, we cannot rely on it to remain static.

---> Why do we need all these different graphic files? To avoid a duplicate definition error and have EQ crash when the second <TextureInfo item="ournewgauge.tga"> is loaded.

---> What do I mean by a namespace? Currently things in the inventory window, for example, are all named IW_*. The "IW_" puts all things in the inventory in the correct namespace. That is so that a AAXPGauge in the inventory window cannot be confused with an AAXPGauge in the AA window. They'd be named IW_AAXPGauge and AA_AAXPGauge, respectively (or something like that).

---> What's wrong with this? First off, we've got too many definitions of the same thing floating around, which is just wonderful for those systems already low on memory from EQ itself. Additionally, where the processor could just cache the page with said graphic in it, it's got what, 6 or 7 different copies of the graphic around, which means more page faults and slower UI updates. We also have a disk storage issue too. If each graphic is 127KB, we now have about 1MB of data where we should be able to get away with 127KB. (When it's loaded, this issue duplicates itself in main memory).

---> What else is wrong with this? Well, now instead of a copy of EQUI_Animations.xml in our custom folder, we've got a copy of every window with a gauge in it. We're now protected if SOE adds an animation, but now if they update any of the windows with a gauge in it, we're screwed. Additionally, if they make a new window with a gauge or put a gauge in a window that didn't use to have one, it'll show up with the old graphic. Bummer.

3) So, we don't want to touch EQUI_Animations.xml, and we don't want 7 copies of the same graphic in memory. Now what? Well, we can make our own customgauge.xml file. But then we need to put an include statement once and only once. The only place to really do that is in EQUI.xml. So now we copy out EQUI.xml, add the include line and away we go. We still need to copy each window with a gauge in it, but at least we don't have to make a different copy of the graphic for each.

---> What now? We haven't solved the problem of if SOE updates a window with a gauge in it or adds a new one. But at least we solved the memory issue from #2.

---> Anything else? Sure. Now if SOE adds another window or anything else they decide to include from their EQUI.xml, we won't see it. If a patch does this, we're broken. The best temporary fix for this is to create a new EQUI.xml file, and include your new file and "EQUI-old.xml". Then regularly and frequently copy out default/EQUI.xml to EQUI-old.xml. Now we're at least covered, but this is bloody annoying.

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

So, what do we want to be able to do?

First, let's take a look a the following XML:
Code:
<Ui2DAnimation item = "A_GaugeFill"> <Cycle>true</Cycle> <Frames> <Texture>window_pieces01.tga</Texture> <Location> <X>110</X> <Y>20</Y> </Location> <Size> <CX>100</CX> <CY>8</CY> </Size> <Hotspot> <X>0</X> <Y>0</Y> </Hotspot> <Duration>1000</Duration> </Frames> </Ui2DAnimation>


That's essentially the same as the C++ code:
Code:
A_GaugeFill = new Ui2DAnimation(); A_GaugeFill.Cycle = true; A_GaugeFill.Frames[0] = new Frames(); A_GaugeFill.Frames[0].Texture="window_pieces01.tga"; A_GaugeFill.Frames[0].Location.X=110; A_GaugeFill.Frames[0].Location.Y=20; A_GaugeFill.Frames[0].Size.CX=100; A_GaugeFill.Frames[0].Size.CY=8; A_GaugeFill.Frames[0].Hotspot.X=0; A_GaugeFill.Frames[0].Hotspot.Y=0; A_GaugeFill.Frames[0].Duration=1000;


Which is essentially the same as this object model:
Code:
A_GaugeFill (Ui2DAnimation) |-----Cycle (boolean) = true |-----Frames (collection) |-----Frame 0 (structure) |-----Texture (string) = "window_pieces01.tga" |-----Location (structure) | |-----X (int) = 110 | |-----Y (int) = 20 |-----Size (structure) | |-----CX (int) = 100 | |-----CY (int) = 8 |-----Hotspot (structure) | |-----X (int) = 0 | |-----Y (int) = 0 |-----Duration (int) = 1000


So we're really just setting values for properties. And since everything has a unique name ("A_GaugeFill" in this case), we can easily reference it when we need to use it later.

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

Why did I go through all this explanation? Because this is how the computer interprets this XML code. If I were to give the above object model to one of you and say, "Please change the Texture of Frame 0 of A_GaugeFill to be 'window_slices01.tga' now," you'd all be able to follow what I said and make the change. You wouldn't cry because I'm just asking you to make a change in the existing model, and it's no problem at all.

Why does EQ cry then? There's no provision to make changes to existing objects. So I have to tell you "Please create a new A_GaugeFill and have the Texture of Frame 0 be 'window_slices01.tga'." But you know that there can only be one thing called A_GaugeFill, and there already is one. I'm insisting that you create another, you don't know what to do, so you just give up and crash.

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

What do we want to do then? We want to be able to "overload" or "override" or "change" objects from the default UI without specifying definitions for 500 other objects (like in my EQUI_Animations example above). As I see it, there are four ways to do this, three of which worth mentioning (the last is complicated and actually makes authoring stupidly difficult, so I just won't bother):

A) The "easy" way. Every time we specify an object with the same name as one already created, OVERWRITE its definition, don't create a new one. We should first load the default interface definitions, then load everything from the custom directory on top of it as a set of overrides, not a set of mutually exclusive files.

This is great and all, but it's starting to look like the kind of problem Turlo was talking about. What happens when we load "EQUI_PlayerWindow", overwrite the gauge definition, but then we load "EQUI_GroupWindow" that wants to overwrite the same gauge definition. Well, this implementation says that all "gauge"s will have the same look, and this just promotes lazy coding. Now it's whoever that gets loaded last that has the final say. So the player window gauges look like the group window wants them to, not the way they're designed.

Now of course, this *shouldn't* happen, because globals should only be globally overwritten if we want to change everything, in the case of a "gauge makeover" mod or something. This is why we have the "IW_" namespacing in the first place. The author of the group window above should make a new gauge definition called "GW_A_GaugeFill" and update things accordingly.

This leaves a lot to be desired though, such as a way to change the look of one of the animations of the group-window gauge without specifying the definition of the group-window gauge itself, in case SOE decides to change the way it works (maybe an EQType number or something a la buff-window!).

B) The "smart" way. This is actually an extension of "A" above, but fixes some of the problems I talked about. Take the implementation of A, but for the sake of all things holy, don't make everything global! Currently there's really NO concept of scope in terms of the top-level objects. And there's no way to actually put anything truly inside a window as far as scoping goes. This is why we have to put "IW_" before everything that belongs in the inventory window. What we do is first create a global object, then we tell the global window to include said object as a piece.

What we should do is have a global EQUI_Animations.xml file, and then things in EQUI_InventoryWindow, for example, should automatically contain the scope "InventoryWindow". So now if we change A_GaugeFill within EQUI_InventoryWindow, it doesn't affect, say, A_GaugeFill within EQUI_PlayerWindow.

This way, we can now have different pieces that both want to overwrite a global do so without breaking each other.

B.5) The "smarter" way. Take "B" above and let us specify our own "EQUI_custom.xml" that adds new include statements to use IN ADDITION to the default EQUI.xml. That way we now have a place we can add our own include-once's. Now we don't need all the different copies of the new overridden definition in our local scopes, we can create our own globals! We can just include-once a new definition of "A_GaugeFill" and then all our windows are updated accordingly.

Now we don't risk breaking everything by copying EQUI.xml just to add our own global include-once list.

C) The "and now for something completely different" way. Each object gets its own XML file. Period. The file "A_GaugeFill.xml" will and only will contain the object called "A_GaugeFill". If you try to define "A_GaugeEndCapRight" in "A_GaugeFill.xml", EQ will spit out an error and not let you. This completely eliminates circular reference errors, multiple definition errors, and the need to explicitly "include" files. In addition, we don't need to devise a new method for overriding elements, we can just use the current "if the file's in the custom folder, use that one instead" scheme.

So, in this new scheme, when we load "PlayerWindow.xml" which will include our window object called "PlayerWindow", we're going to come across a tag "<Pieces>PW_HPGauge</Pieces>". First we check if we've already loaded something called "PW_HPGauge". If yes, we just place that object in the window. If no, we look for a file "PW_HPGauge.xml" in the custom UI's directory. If it's not there, we go to the default directory and look there. If it's not there, we spit out an error that we couldn't find the object "PW_HPGauge".

Now, say we want to change the way all our gauges look without changing anything else (as per our original example). All we have to do is replace "A_GaugeFill.xml" with a new one. No hassle, no mess. If SOE adds a new animation to the default directory, no problem. If they change a different animation, we haven't touched the original definition so it's ok. If SOE adds a gauge to a new window, we're still fine since we've overwritten the global definition of what a gauge should look like.

I know, you're asking "What about TextureInfo tags? They don't have a name!" Well they kinda do have a name - the filename of the graphic file. Put the tags each in a separate *.textureinfo file. Like window_pieces01.tga would have a new file "window_pieces01.tga.textureinfo" that contains the textureinfo tag for that graphic.

If you haven't figured it out, even though this explodes the number of files we need, I think this is the best way to handle this problem.

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

To conclude and comment on the original post:

I have to completely agree with Angahran and say that I really wish SOE implemented this sort of thing. It's actually pretty sad to think that they haven't, and while I won't go so far as to call it a sloppy implementation, I will say that it's a real shortcoming in UI modification. It leads me to believe SOE wants everyone to either completely change the entire UI or leave it alone altogether. And either they forgot about how patches can break our code when we have to modify everything, or they actually believe we're going to sift through their (pretty much entirely uncommented, so far as I've seen) default UI code to figure out what's changed, and then update our own code to incorporate what they did.

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

And now for other things that have been as yet unaddressed:

Turlo - We're talking apples and oranges here. I agree with what you're saying, but it's not what Angahran was talking about. You mention a naming convention problem, which is true whether a system of overloading is in place or not. This has nothing to do with working with other peoples' work, this has to do with working with the default code, which theoretically can change without notice and wreak havoc on our custom UIs.

Daggun - Yes and no. While EQ does allow you to modify UIs on a file-by-file basis, it doesn't allow you to modify UIs on an object-by-object basis. Thus (as in my example) if you want to change one thing in EQUI_Animations.xml, you have to essentially commit the other 500 definitions in it to be forevermore static. AND, you have to willingly say "I hereby wish to ignore any new animation objects added to the default EQUI_Animations.xml". This is a cause of a lot of things breaking.

Mahein - We don't need line numbers anymore because we're in an object-oriented world now and every object has a unique name. Take A_GaugeFill for example. This is an object of type "Ui2DAnimation" with a bunch of properties defined for it (e.g. Cycle (boolean), Frames (a collection of structs)). Take a look at my XML/C++/Object example above. We know exactly what line we want to replace by specifying the object by its globally unique name ("A_GaugeFill" in this case).

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

If you've gotten this far, thank you for reading, and I hope this both clears up some issues in understanding and provides some insight as to where to go next. Whether anything will happen because of this, I don't know, but at least I'm happy I've given my 2cp. Please try to find problems in this implementation and point them out. What do you guys think?

If something doesn't make sense (it is am3:30 as I write this), please ask me and I'll clarify. Apologies in advance.

-lc
lcalabrese is offline   Reply With Quote