Jump to content
  • Advertisement
Sign in to follow this  
GenuineXP

Abstraction and Enumerations Across Different APIs

This topic is 4411 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello. What's the best way to deal with enumerations that represent the same thing but are given different values between APIs in an abstracted design? For instance, I have an abstract class called Image that provides some methods for working with an image file loaded from disk (or soon from any data source/stream). Image provides some methods that return enumerations (int's), such as getType() and getFormat(), which return the pixel data's native type (i.e., byte, float, etc.) and return the image format (i.e., RGB, Luminance, etc.) respectively. I've created some enumerations of my own for my engine to act as middlemen. That is, I can "translate" one APIs enumeration into one of my own. DevIL may return IL_UNSIGNED_BYTE which can be translated into IMG_UNSIGNED_BYTE. With this scheme, objects must translate their native API enumeration into a Mocha one (Mocha is what I'm calling my game engine). This way, objects that receive the enumeration know what it means, regardless of what API they're using, by simply translating it into their native enumeration. To do this, I have a little EnumTranslator class full of static methods that... well, translate all sorts of enumerations into Mocha enumerations and back. This seems pretty clunky to me. Is there a better way to work with differing enumeration values? Writing all of these functions with huge switch statements seems a bit wrong. :-P Thanks!

Share this post


Link to post
Share on other sites
Advertisement
I would think your wrapper should do the work of translating data types. That is, your game engine Mocha should never even see a constant from devIL. If you have an image loading class that you implement with devIL, that class should know how to use devil AND should know what mocha wants. If you make another image loading class that implements the same interface with imglibx instead of devil, then that class will (and should, it makes sense!) know about both imglibx and Mocha.

I think you're missing the point if you are passing library specific codes into Mocha and then translating them into Mocha codes with a big long switch statement or something. The only places you'll need to translate codes are at the actual interface between devIL and Mocha - and furthermore, no class will ever need to talk to both devIL AND imglibx, so there's not much point in a centralized function.

Share this post


Link to post
Share on other sites
Also, where possible, don't do the translation with switch statements; do it with array lookup.


enum { x_foo, x_bar, x_baz } x;
enum { y_baz, y_foo, y_bar } y;

y x2y(x an_x) {
y lookup[] = { y_foo, y_bar, y_baz }; // i.e. matched up to the x enumeration
return lookup[an_x];
}

x y2x(y an_y) {
x lookup[] = { x_baz, x_foo, x_bar }; // i.e. matched up to the y enumeration
return lookup[an_y];
}

Share this post


Link to post
Share on other sites
I like to use if statements or switch statements instead of array lookups. I find them much more readable, and they also have the benefit that if the external API decides to switch the values of the enumeration, my code doesn't need updates as it would using array lookups. They also return specially-typed values instead of plain ints.

Share this post


Link to post
Share on other sites
Quote:
Original post by rileyriley
I find them much more readable


Repetitive things are "more readable"? :s

, and they also have the benefit that if the external API decides to switch the values of the enumeration,[/quote]

How exactly would that happen? You'd have to either grab a new version of a DLL (and DLL authors are *very* reluctant to break "binary compatibility" in this way), or get changed source. Or you could, you know, just stick with the version of the API you're already using?

Quote:
my code doesn't need updates as it would using array lookups.


Let's evaluate this claim.


y x2y(x an_x) {
y lookup[] = { y_foo, y_bar, y_baz }; // i.e. matched up to the x enumeration
return lookup[an_x];
}


Do I need to change the code if the Y enumeration changes? No. I specified the array contents via enumeration values.

Do I need to change the code if the X enumeration changes? As it stands, yes.

But, if I'm paranoid, I could set up the array with individual assignments instead:


y x2y(x an_x) {
y lookup[3]; // In the general case, we should have a smarter way of
// counting those values, and we should do the setup work just once somewhere.
lookup[x_foo] = y_foo;
lookup[x_bar] = y_bar;
lookup[x_baz] = y_baz;
return lookup[an_x];
}


Now, how does the code quantity compare to what we'd have with a switch?


y x2y(x an_x) {
switch (an_x) {
case x_foo: return y_foo;
case x_bar: return y_bar;
case x_baz: return y_baz;
}
}


Pretty similar. But instead of just repeating an array assignment, the switch repeats a case: label and a return keyword. Of course, there exist coding standards that wouldn't let you write the switch so compactly...

Anyway, I lose basically nothing; and I gain the ability to write compactly and avoid quite a bit of repetition in some cases (I would argue "most cases"; you should need to be paranoid relatively seldomly).

Quote:
They also return specially-typed values instead of plain ints.


So does my solution. Nothing prevents you from making an array of enumeration values, and the elements of an array-of-enumeration values are, indeed, enumeration values and not "plain ints".

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
Quote:
Original post by rileyriley
I find them much more readable


Repetitive things are "more readable"? :s


First of all, it's not the repetition that I was referring to. With a case statement, you have an explicit translation for each value. This is of course more readable than a list that infers its translation values from its order. For example:

 y x2y(x an_x) {
y lookup[] = { y_foo, y_bar, y_baz }; // i.e. matched up to the x enumeration
return lookup[an_x];
}


what does x2y(x_cetera) return?

As a programmer, in order to find out, I would need to look at the definition of an_x in an_x.h. Or maybe it's defined in another file? Who knows - hopefully my intellisense is working.

 y x2y(x an_x) {
switch (an_x)
{
case x_cetera: return y_foo;
default: return y_invalid or throw invalid type exception;
}
}


What does x2y(x_cetera) return? y_foo, clearly. This is easier to read.

Notice I have the extra benefit that I didn't even mention in my last post: an invalid type passed to my function will no longer make my code responsible for an access violation.

Second of all, you act as if "repetition" in this sense is bad, somehow. It takes longer to type, I guess, but it's not CODE repetition - these instructions are all separate and unique. The extra time it takes to run through 20 je commands is only going to hurt your performance significantly in rare cases.

Quote:
Quote:
and they also have the benefit that if the external API decides to switch the values of the enumeration,


How exactly would that happen? You'd have to either grab a new version of a DLL (and DLL authors are *very* reluctant to break "binary compatibility" in this way), or get changed source. Or you could, you know, just stick with the version of the API you're already using?


This seems so clear to me that it makes you seem almost antagonistic. Say I'm using a library that is not in a finished state - or, even worse, is just starting to be created. Programmer X from the other team decides to add a new type to his enum, and he does it in the middle (either because the values need to be in a certain range to ease up on a sorting algorithm, or because the values are more readable when grouped together in a certain way, or because he's dumb - who cares WHY he does it). Your code is broken, and mine isn't. This is not an impossible case. Sure, I could never upgrade the API, but if I want the functions that weren't implemented in the first version to work, I might like to. My product manager would also probably appreciate it if we could ship with a single copy of the DLLs, and not need separate copies with different versions for each programmer.

The DLL programmer should be able to change enum values without breaking code that uses the DLL. Otherwise we'd just use #defines.

Quote:

Quote:
my code doesn't need updates as it would using array lookups.


Let's evaluate this claim.


y x2y(x an_x) {
y lookup[] = { y_foo, y_bar, y_baz }; // i.e. matched up to the x enumeration
return lookup[an_x];
}


Do I need to change the code if the Y enumeration changes? No. I specified the array contents via enumeration values.

Do I need to change the code if the X enumeration changes? As it stands, yes.


Right, so we're agreed that the first version you posted is unacceptable.

Quote:

But, if I'm paranoid, I could set up the array with individual assignments instead:


y x2y(x an_x) {
y lookup[3]; // In the general case, we should have a smarter way of
// counting those values, and we should do the setup work just once somewhere.
lookup[x_foo] = y_foo;
lookup[x_bar] = y_bar;
lookup[x_baz] = y_baz;
return lookup[an_x];
}


Now, how does the code quantity compare to what we'd have with a switch?



You're right again. Now that we've worked around half of one reason your code is unacceptable (by adding the "repetition" you scorned in my code) your code is more readable. However, even though you call this new code paranoid, it's still subject to error when the enum changes.

Quote:

y x2y(x an_x) {
switch (an_x) {
case x_foo: return y_foo;
case x_bar: return y_bar;
case x_baz: return y_baz;
}
}


Pretty similar. But instead of just repeating an array assignment, the switch repeats a case: label and a return keyword. Of course, there exist coding standards that wouldn't let you write the switch so compactly...



Pretty similar except my code will never break and your code is a threat to the stability of the program.

Who cares how compactly you write it?

y x2y(x an_x)
{
y returnValue;
switch (an_x)
{
case x_foo:
returnValue = y_foo;
break;

case x_bar:
returnValue = y_bar;
break;

case x_baz:
returnValue = y_baz;
break;

default:
returnValue = y_invalid or returnValue = -1 or throw InvalidTypeException;
break;
}

return returnValue;
}


Ok, so I've taken 10 times as many lines as you. Is this bad programming now? Especially with intellisense, I can type this in 30 seconds. And it never breaks, even with changes to the enums. An API user can break your code even with NO changes to the enums.

Quote:

Anyway, I lose basically nothing;


I disagree - see above

Quote:
and I gain the ability to write compactly


This isn't important to me

Quote:
and avoid quite a bit of repetition in some cases


It is YOU who are repeating code. My code has many similar lines in it, but each one is independent and does a different thing. Your code relies on the definition of the enum to be aligned with the definition of your translation array. THAT is the dangerous kind of repeating code -repeating data that can be desynchronized. Mine is verbose, but yours can be contradictory.

Quote:
(I would argue "most cases"; you should need to be paranoid relatively seldomly).


..be paranoid relatively seldomly? I can't believe you say that. This isn't extreme paranoia - your code will cause access violations if called improperly even when no enums are changed and will silently return the wrong values if the enumerations change. Those are BIG PROBLEMS that might be very hard to find later, and you can fix them up front with an extra 30 seconds.

Quote:

Quote:
They also return specially-typed values instead of plain ints.


So does my solution. Nothing prevents you from making an array of enumeration values, and the elements of an array-of-enumeration values are, indeed, enumeration values and not "plain ints".


You're right about that - not sure what I was thinking.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!