Here goes.... fund. design ? for gfx library

Started by
7 comments, last by blahpers 22 years, 8 months ago
Yeah, like everyone else, I''m throwing together an API-independent graphics library (C++, all nice and OO), and, as usual, I''m having problems at the design level. (Hey, coding''s easy, but good clean design is an art form.) It boils down to this: Most (okay, probably all) APIs require you to call one or more initialization functions before you''re ready to play with graphics. (Example: DirectX Graphics requires you to create a Direct3D object, a Direct3D device, a sprite interface if you need it, etc.) And, well, I''m tired of all that, so I was going to throw it all in a Vid::Init() function so that I''d only have to call that. ''K. Then, I got to thinking: couldn''t I do that in the constructor? That way all I have to do is just declare an instance of Vid (maybe global, tuck it away in a header file) and I''m all ready to go. But, well, what happens if it fails? The constructor has no return value of course, so I''d have to place the Vid object in an "invalid" state, which I''d have to check before I could do anything, and it''d get all messy. So, I kept thinking (I really shouldn''t do that). What about exceptions? Just have the constructor throw some exception if it botches. I dunno. Maybe I''m just biased against exceptions, maybe I just haven''t practiced them enough to get a good feel for them. Should I even try doing everything in the constructor? I''m not too fond of the Init() idea because there''s always the possibility of someone trying to render before calling Init(), so every function would have to check "if (initialized_) {}" or something. That''s a lot of overhead if you do it every time you draw a triangle. Should I bite the bullet and make it all exception-friendly? It would be nice to have functions with real return values again (instead of "ErrorCode GetPixel(int x,int y,Color& color)", I could do "Color GetPixel(int x,int y) throw(etc.)"). Is it OOD-ethical to even have a global video object (like Vid viewport(etc.))? If I used the constructor method, I couldn''t really do this anyway because I wouldn''t have the appropriate information to pass to the constructor at the time (viewport size, resolution, phase of the moon...) Also, OOD tends to frown upon globals. A plethora of questions for a single messed-up mind. Have fun. --- blahpers
---blahpers
Advertisement
I use the Create or Init function instead of using a constructor for initialization for the reasons you gave: no returns and global and member data initialization problems. For all your exception and error checking stuff -- why not use assert or a varient thereof. Then you get all the checking in debug mode -- and all the speed in release.


Jack
quote:Original post by JackNathan
I use the Create or Init function instead of using a constructor for initialization for the reasons you gave: no returns and global and member data initialization problems. For all your exception and error checking stuff -- why not use assert or a varient thereof. Then you get all the checking in debug mode -- and all the speed in release.

Ick, you still have to have error checking in release. Who knows what crappy hardware they''re gonna be running? I''m trying to keep error checking pretty simple, but if the thing crashes upon init (like so many graphics proggies do), I''d like to have some idea what happened, too. Exceptions are probably it, then.

Asserts? Can''t stand ''em. When I code an assert, it feels like I just slapped some spackle on and hoped nothing went wrong. *heh* They''re just kinda sloppy. I like how exceptions automatically wrap things up for you instead of bailing straight out.

---
blahpers
---blahpers
Continue with your Initialization functions, and pair them with Shutdown functions. This will allow you to open/close your GUI windows at will without having to delete and re-new a window object.

It also keeps exceptions from being thrown in your constructor. This is a bad thing because if you call new on any pointers in your constructor and AFTER an exception is thrown while still in your constructor, the object won''t be constructed, thus the deconstructor will never be called. This means that whatever pointers you new''ed will never get deleted, hence you''ll get a memory leak. There are ways to avoid this, it''s best to just not use exceptions in constructors.

IMO, you should only use exceptions for ''exceptional'' cases, as in where your program cannot continue to run. For other normal error messages I''d use return variables.

Also, instead of your ''if (initialized)'' statement, use ''ASSERT(initialized);'' because not calling Initialize would be a programmer error, thus if it works in debug it''ll work in release. ASSERT statements are only executed in debug builds, not release. Always use ASSERT for anything that could be programmer error. Use if statements for anything that is inputed from an outside source (user, files, etc).

As for a global object, I wouldn''t. Use Singletons or pass the instance to the class. You can do a search on Singletons on this site if you don''t know what they are.


- Houdini
- Houdini
Stupid C++ FAQs book.

It goes through several reasons on why constructors should throw exceptions, mentions that some authors suggest otherwise, then (between the lines) pretty muchs suggests that said authors are fools. The book, however, doesn''t address the point that resources allocated by the constructor have no chance of being released. I suppose you could fix this some of the time by try/catching the constructor body.

So, Init()/Uninit(). As far as using exceptions in Init()/Uninit(), well, in many cases failing to initialize does in fact kill any chance of continuing the program, so exceptions are fine. Many other graphics routines also fail only in the case that something really bad happed to the graphics interface (ie, some other app stole a vital resource, etc.), so I don''t mind using exceptions here either. Not to worry, I don''t plan on throwing an exception just because I tried to render to an unlocked surface. Mainly, I''ll probably just use exception-handling to close the API (done automatically during stack unwind), spit out an error message, and quit.

Ergh, really don''t want to use ASSERT, that''a an MFC thing, and I''m trying to stay away from MFC. Also, I want to be as platform-independent as possible, so if I port this thing I won''t pull all my hair out hunting down ASSERTs and changing them. The C "assert" is more portable, but not versatile; you can''t tell it what to report. I see the benefit, though, since the check is completely removed for release.

*looks up singleton classes* Singletons are goooood.

Thanks you two, so far this is helping a lot.

---
blahpers
---blahpers
Actually, checking back, why couldn't you just wrap the constructor body in a try block:

try  {    // Initialize the API  }  
And then, if any exceptions occur, free anything that did get allocated and rethrow the exception:

catch(...)  {    // Free any allocated resources, delete any non-NULL pointers    throw;  }  
?

---
blahpers

*edit: to make it look pretty*

Edited by - blahpers on August 16, 2001 4:40:07 PM
---blahpers
blahpers, yes, you could put a try/catch pair in every constructor of every class. You''d also need to duplicate the deletion of variables (one in the catch of your constructor and the other in your deconstructor). That''s a whole lot of duplicate code. Also, remember you pay a small performance hit for every try your program executes.

Be aware as well, that if you require that users send the necessary class information to initalize the class in the constructor, then you limit how the class can be used. For instance you couldn''t do this:

MyClass arrayofclasses[8];

Because you can''t specifically send parameters to each class in the array. Instead you''d have to use pointers and new each instance seperately, which isn''t a bad thing really of course but you''d unnecesarily increase your chances of a memory leak (must remember to use delete[]) and you''d be limiting how your class can be used.

And, as I''ve mentioned before, you won''t be able to start/stop your class at will, which is another limitation.

Let me show you how I used this in my API-independant graphics library. I used the Bridge pattern to make my graphics/audio/input systems API-independant. I''ll try to briefly explain how this works, but you can probably do a search on GameDev to find out more about this pattern, or pick up the widely renowned Design Patterns: Elements of Reusable Object-Oriented Software.

Basically, it works like this:

You have a pure abstract GraphicsBase class that you derive your concrete API specific classes from. This gives each API the same interface. You never call these classes from your game tho, instead you create a Graphics class with the exact same interface. This Graphics class would use the concrete classes. It would look something like this:

class Graphics
{
protected:
GraphicsBase *m_pGraphics;

int m_iHeight;
int m_iWidth;
int m_iBPP;

int m_iRenderMode;
in m_iShadingMode;

public:
bool Initialize(int iAPI, int iHeight, int iWidth, int iBPP);
bool Shutdown();

bool Draw();
bool SetRenderMode(int);
bool SetShadingMode(int);
...
};

In the Initialize class you could pass which API you''d like to use:

Graphics::Initialize(int iAPI, int iHeight, int iWidth, int iBPP)
{
m_iHeight = iHeight;
m_iWidth = iWidth;
m_iBPP = iBPP;

switch(iAPI)
{
case API_DIRECTX:
m_pGraphics = new DxGraphics;
break;
case API_OPENGL:
m_pGraphics = new GLGraphics;
break;
default:
ASSERT(FALSE);
}

m_pGraphics->Initialize(m_iHeight, m_iWidth, m_iBPP);
}

Then every other function (Draw, SetRenderMode, etc) would just call the m_pGraphics version of the same function. What does this allow us to do? It allows us to switch between OpenGL and DirectX on the fly, ala Q3. If we had the initialization code in the constructor, then you''d have to delete the class, and create a new one, thus losing ALL information stored in the class (width, height, bpp, render mode, shading mode, etc, etc, etc).

Anyways, you can either initialize your object in the constructor, or not, it''s up to you. I just find my classes more dynamic when I put that code in an Initialize function.


- Houdini
- Houdini
Nice. OK, you've won me over. I might use the constructor try/catch thing for some other classes, but not for this.

Still, the Graphics class seems kinda redundant--all the member functions just call m_pGraphics->SameFunction(), with the exception of Initialize(). Can't think of a better way to do it, though, so *applause*.

Still not sure about using ASSERT(). Maybe I'll rig up a macro to do pretty much the same thing, but with more informative error reporting.

Thanks very much!

Edit: adding more stupid questions

Edited by - blahpers on August 18, 2001 12:46:15 PM

Edit: removed questions 'cause they were stupid

Edited by - blahpers on August 18, 2001 12:46:59 PM
---blahpers
As for ASSERT... first of all, what I said was not what I meant. Obviously, you have to return and check error codes. What I was meaning is in addition to use ASSERT''s to catch your errors. I use _ASSERTE a great deal. With that you get the line number and the code caused the problem in the dialog box that pops up. It''s caught a number of array index errors for me in the last month.


Jack

This topic is closed to new replies.

Advertisement