Advertisement

Interfaces, inheritance, etc

Started by June 10, 2000 11:32 AM
21 comments, last by Kylotan 24 years, 6 months ago
quote: Original post by null_pointer

Anyway, I should think that it might be good to use the address instead of the ID, but you would have to dynamic_cast it each time (and in the debug version, you could check for NULL pointers). That might be comparable to the abstract base class method, but I''m not sure. To use this method, you only need the following class:


class Graphics {};



Right now, I have nothing of importance in my Graphic class, so it is like that. Later I might implement a GetName() function and other trivialities.

quote:
Why do you even need a hierarchy for the drawmanager and graphics classes? I just think that you are not thinking of the best solution here. IMO, you shouldn''t need either dynamic_cast-ing or IDs in a good system.


IDs are good as they can provide an extra level of abstraction. If you always use pointers then you make it difficult to move things around. Usually objects are stored within a collection within another object. For the ''outside world'' to hold a reference to an object, it would need a pointer or an ID. What if the container I was using happened to be a vector, and I deleted an object from the middle? Some of the pointers would be invalidated, even though the objects they refer to haven''t really changed. They just happened to be moved around through some implementation detail.

In fact, that sort of reason makes me think even more that I should be passing around some sort of GraphicID. Remove the casting and improve the encapsulation in one go.

As for no good system using dynamic_cast... well, I don''t really want to get into that Suffice it to say that (a) many well-respected authors and programmers have well-documented reasons for using it, (b) not all well-respected programmers are good programmers, and (c) I''ve never needed to use one before anyway.

The reason I ''need'' a hierarchy for the DrawManager stuff is because the rest of the game logic should be able to say:

"Please draw this Creature at X,Y on the screen"

And this should work regardless of the implementation of the graphical subsystem. Therefore, to enforce a common interface to such drawing functions, I made an abstract base class DrawManager, so that implementations of the different graphical APIs would inherit that common interface.

Ok, to sum up...
I am not sure that I need a hierarchy for Graphic*. But that was one thing I had considered, along with templates, and now IDs, to try and link a specific Graphic type to a specific DrawManager type. The ''Graphic'' class doesn''t do anything, it is just an object that holds a pretty picture with whatever API-specific information is needed to for that API to render it. I thought it might be possible to provide an abstract interface to Graphic so that a DrawManager could just take a Graphic and draw it using the interface. But since the interface, from the base class, can never return specifics such as LPDIRECTDRAWSURFACE, that approach doesn''t work -for my purposes-.

Either way, I don''t quite have the same goals as you here: I don''t have to have a perfectly elegant solution which is going to be evaluated and tested by millions (a bit of helpful optimism for you there ). Rather, I want to get it working and quickly so I can write my game! Elegance tweaks can happen later. I expect lots and lots of amateur projects by fairly experienced programmers fail because they spend too much time trying to make it perfect and not enough time trying to make it work.

quote:
(Personally, every time I call any class X manager I''ve got a bad design -- usually a vague task I don''t know enough about (which, for me, means everything). For me, it usually takes about 2-3 tries (meaning semi-working implementations) until I get the right design. Sometimes my friends give me ideas, etc.)


I wouldn''t agree with this: having some sort of Manager class shows that you are able to encapsulate an entire subsystem behind one simple interface, which is usually a sign of good modularity and has good potential for code re-use. It''s also an example of the Facade pattern. (Note that I am starting to religiously quote Design Patterns in my newly-acquired Saint-hood. Largely cos, although lots of people disagree with my ideas as -I- express them, no-one seems to disagree with that book )

quote:
So here''s (yet another) solution:

Create a blitter class and a surface class, which basically just move memory and provide some kind of access to it, whatever form in which it is stored. Create custom engines (2D, 3D, etc.) which build on those two generic classes. ("build" here does not mean a hierarchy -- just use the existing classes)

DirectDraw, for all of its quirks, provides a very good system for both 2D and 3D. 3D really just uses the 2D methods (and adds some custom exceptions of its own), and this is basically how all video cards work. That''s why I''ve chosen it for vos, ok?


I''m not sure you are appreciating what I am trying to do. I''m not trying to achieve 2D and 3D functionality through one simple interface. I''m trying to achieve the same 2D functionality through various APIs. If you just pretend that I want to replace Blt() with DrawPrimitive() but to show exactly the same thing on-screen, you''re on the right track. But this requires that surf

quote:
About vos. I said that the surfaces can be created in system memory. The only way they can be created in video memory is by the video* interface. Each person who writes an implementation for the video* interface writes the implementations for the video::surfaces, video::surface::clippers, etc. and they are all in hardware (where possible).


Someone using your system could decide to derive a single implementation from your interfaces, and make them platform dependent to take full advantage of hardware, etc. Because they will probably just derive 1 implementation, they can be cross-dependent. Whereas I need all these APIs to be able to coexist within the same program at the same time, so that you can choose your API from the start up menu or something. The only thing that needs to stay standard is that I need to be able to ask the DrawManager, whichever type it is, to do certain basic things, given the appropriate graphic.
Thanks for the optimism!!

OK, there are three types of people involved in your system: you, the user of the library (which might be you too), and the player of the game.

Seriously, I think I have the solution to your problem. It's a mini-version of what I'm doing (but -really- easy to implement). Give the DrawManager class a virtual method that allows it to get input from the user (like DialogBox() in Win32), and allow the user to configure it. Call that virtual function "Configure()" or something like that. That way the user of your library doesn't have to implement dialog boxes and what-not -- your library can do it transparently because you control the code that goes into Configure() for each class derived from DrawManager. In other words, you are skipping the user of the library and going directly to the player of the game.

Now, the user of the library needs to know nothing but the file names of his images, a collection of IDs, and some basic drawing commands, like "draw this here" or something similar. Color depth, resolution, etc. can all be abstracted away from the user of the library, and it's really not that hard to do it this way.

Oh yeah, to make all this easier, put the DrawManagers into dynamic-loading DLLs, and export the DrawManager interface. Kinda like how plugins work. Then enumerate them at startup and allow the player of the game to pick one, bypassing the user of the library yet again.

More power to the DrawManagers!

(...or you could just wait and use vos when it's out...)

Lots of luck to you -- I won't take up your time with any more theories!




- null_pointer
Sabre Multimedia


Edited by - null_pointer on June 13, 2000 5:20:39 PM
Advertisement
Ok, to sum up, and to give any ideas to anyone lost on how to make such a system...

I am almost definitely going to change anything that currently passes a pointer to a graphic around, to passing a GraphicID (most likely a typedefed unsigned int.) This removes any casts, allows me to move the graphics around in memory without invalidating anything, yet still allows ''external entities'' to hold ''handles'' to certain graphics.

When an entity is created, it will most likely have a filename associated with it, signifying the graphic to use. It then calls a function like this:

GraphicID DrawManager::GetGraphicID(String filename);

This will load the file in and create the surface if needed, and return the GraphicID that is associated with that surface to the entity, which stores that GraphicID for later reference.

To draw an entity, you call the Blit function in DrawManager (there are actually 2 of these functions: 1 to clip to the screen area, and 1 to clip to the game area. I may combine these later) and pass it the relevant details. This looks like:

vitual bool DrawManager::BlitGraphic(GraphicID id, Rect location);

Internally, the implementation translates the ID to the relevant representation of a Graphic for that implementation, gets the platform/library specific surface information from it, and calls the platform/library specific function to copy that data to the back-buffer, by using DirectDraw''s Blt, or D3Ds DrawPrimitive, or whatever. If an ID is invalid, I can draw a rectangle of a random colour for a visible error effect, or log the error, or even throw a dreaded exception if I need execution to stop. But there will be no null_pointer errors

Later, I may add extra parameters (which have default values so as to not break the old code) to the BlitGraphic functions, but that will only be if I need to do so.

No class except DrawManager now needs to even know of the existence of any Graphic class. All they need is to be able to hold onto their lil GraphicIDs.

Hope this helps anyone trying to do something similar for themselves. (Probably not, but oh well.)


Oh, and it appears a bit of my last post was missing: it should have read: "But this requires that surfaces are stored in an implementation specific way." or something to that effect.

This topic is closed to new replies.

Advertisement