Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Exceptions in SFML 3 ?  (Read 37315 times)

0 Members and 1 Guest are viewing this topic.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
Exceptions in SFML 3 ?
« on: October 11, 2013, 08:11:04 pm »
This topic was originally following this discussion.

It's not bool or err(), it's usually both ;)

And yes, SFML 3 will most likely use exceptions everywhere.
« Last Edit: October 13, 2013, 03:37:17 pm by Laurent »
Laurent Gomila - SFML developer

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #1 on: October 11, 2013, 08:14:07 pm »
Ah, sorry.  I only started redirecting sf::err() recently because I wanted to see shader compiler errors, so I hadn't noticed loadFromFile uses it too.

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #2 on: October 11, 2013, 08:37:02 pm »
Quote
And yes, SFML 3 will most likely use exceptions everywhere.
Conditionally plz. :-X Even boost filesystem has support for both codes and throwing. Exceptions are too oppressive to be used everywhere. :(
Back to C++ gamedev with SFML in May 2023

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
Re: Exceptions in SFML 3 ?
« Reply #3 on: October 11, 2013, 08:52:03 pm »
What are your arguments against exceptions? Why are they oppressive?
« Last Edit: October 13, 2013, 03:37:34 pm by Laurent »
Laurent Gomila - SFML developer

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #4 on: October 11, 2013, 10:39:11 pm »
1. Screw with C code callbacks, ie. when getting thrown in Lua wrapper when Lua wasn't compiled as c++(not the brightest idea, but doable).

2. Screw with literally every language, so now C wrapper for porting has to catch exceptions and return error code and then let wrapper language do something sane(throw? error code? `rm -rf /` ?) with that code.

3.The fuss of converting error code to exception is way way lesser than fuss of converting exception into error code:

Code to throw:
        if (tex.loadFromFile("pic.jpg")) throw loadFromFileFailed();
 
One liner, literally.

Throw to code:
try
{
        tex.loadFromFile("pic.jpg");
}
catch(loadFromFileFailed& e)
{//by this point I am already kind of discouraged and want to go play with some other library that doesn't force exceptions
        return errcode::loadFromFileFailed;
}
 
No comment, looks stupid, definitely can't be faster, might be slower, pointless as hell.

4. Consider use case of an imaginary unix-y style tool for checking your ready-to-deploy zip/pack/bundle/game.laurent file/folder to ensure all textures load ok in SFML, takes a list of texture paths on stdin and outputs them with info about SFML compatibility on stdout:
int main()
{
        std::string name;
        sf::Texture tex;
        std::cout<<std::boolalpha;//pretty bool printing
        while(std::getline(std::cin,name))
        {
                std::cout<<name<<"loadable via SFML:"<<tex.loadFromFile(name)<<std::endl;
        }
}

With everything-is-throwing-cause-modern-cpp-is-one-size-fits-all:
int main()
{
        std::string name;
        sf::Texture tex;
        std::cout<<std::boolalpha;//pretty bool printing
        while(std::getline(std::cin,name))
        {
                try
                {
                        std::cout<<name<<"loadable via SFML:";
                        tex.loadFromFile(name);//returns void but can throw loadFromFileFailed
                        std::cout<<true<<std::endl;
                }
                catch(loadFromFileFailed& e)
                {
                        std::cout<<false<<std::endl;
                }
        }
}
Consider based on 3. and 4. how much harder it can be to come up with use case where error code you'd prefer to be exception is more cumbersome than exception that you don't want(stupid programmer who ignores returned codes and then has issues about his textures being white and sounds missing because loads actually failed and returned false he didn't check for doesn't count, he could as well ignore result of malloc and have a memory leak, functions return for a reason, he will pray exceptions don't happen or make giants try{}catch(...){} blocks and then have the same problem).

5. Screw with C resources, almost every C library is a c++ one:
some_c_resource * resource=open_something_c();
sf::Texture tex;
//lets say we use that resource to look up name of texture we want
tex.loadFromFile(look_up_value_of_attr_as_str(resource,"texname"));//BTW all your C resources are belong to Laurent if this throws and you don't catch it here, close them and then rethrow, hehe, happy boilerplate writing <3
 

5.1. Yes, you can write wrappers or use template parameter deleter in smart ptr to close C resources OR write non throwing api that returns codes around this one. You probably should wrap your C stuff when you're forced by real user case of your code or want to make your work easier, not by arbitrary design choice of a library made to be more 'modern'. You're fighting the library at this point, go pick up SDL or GLFW or something else.

6. They are explicitly designed to take control away from my code and so they introduce boilerplate and make me fight the library if I don't want something like a texture manager to throw, and even if I did, they'd be my well organised coherent exceptions, not thor and sfml going to town on my scopes with over generalized exceptions thrown from too low to have enough context, 'textureloadingfromfile' failed, yep, I'll totally catch that 10 places in call stack from now on, and totally distinguish that certain texture loading from another one in another module at another time... It'll end up being ignored or used one place up just like an error code would.
A texture manager should just get given a list of data handles that are from disc, ram, wherever via inputstream interface, load texture from each and report how many it loaded, it's not supposed to be verifying anything. If needed it's state(amount of textures actually loaded) can be checked by higher system and then it can throw if needed. Maybe this higher system that uses this class isn't so throw trigger happy, maybe it's that unixy tool that will just check if all textures load ok. Don't assume I want to bail scopes on all failures(because with everything-uses-exceptions you do assume just that, that or that I want to write additional empty catch in case I don't want to bail..).

I really dislike exceptions overuse and find them annoying unless they're done like Lua, Boost and/or used sparsely when it makes sense.
« Last Edit: October 11, 2013, 10:40:43 pm by FRex »
Back to C++ gamedev with SFML in May 2023

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #5 on: October 11, 2013, 11:09:42 pm »
Wow...should we make a separate thread for debating error handling best practices?

Personally what FRex says feels right specifically for loadFromFile, but when I actually think about it there really aren't any typical use cases where a failure to load a file has an obvious and simple alternative within the current function that preserves normal execution flow.

I can think of exceptional use cases, and cases where alternatives would exist higher up the call stack, but of course those are precisely the cases where exceptions are clearly better than return codes.

P.S. I don't understand bindings to other languages enough to have an opinion on that part of the issue.
« Last Edit: October 11, 2013, 11:11:15 pm by Ixrec »

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #6 on: October 11, 2013, 11:39:51 pm »
Quote
P.S. I don't understand bindings to other languages enough to have an opinion on that part of the issue.
You can't propagate exception to any foreign language, some have very different exception strategies or not have them at all(C) so it's performance hit without any benefit + it affects a binding which is inherently at a disadvantage cus it's calling c++ through C wrapper = awesome. Sometimes even trying to pass it in C might make mini-hiroshima in your program, on MinGW DW2 is 32 bit only, bloats the executable, is much faster but breaks winapi callbacks, SJLJ is way slower(10%-30% hit in 'exception heavy code', but it's found on internet so take it with a grain of salt), more space efficient and doesn't break callbacks.

Quote
Personally what FRex says feels right specifically for loadFromFile, but when I actually think about it there really aren't any typical use cases where a failure to load a file has an obvious and simple alternative within the current function that preserves normal execution flow.
Then throw. You can have map of strings to shared pointers, and if loading fails you can assign some very eye sore texture to catch erorr in game(I actually did that irl at one point and plan to do it again after I stop using debug draw from Box2D as my ad-hoc graphics), or throw up and crash entire game. In case you want to throw it's one line for you, in case I want to not throw it's 4+ lines + small performance hit for me.
Also the unix-y program example above wouldn't look good with throws, as shown.
Just keep loading, have a variable to count, do m_successfulloads+=m_map[name].loadFromFile(path); and then check if amount of passes paths is == to m_successfulloads somewhere else or even throw there if any load fails, include name or part of name or index of missing path or something in your exception(but don't allow it's ctor to throw too). 'FailedToLoadFromFile' exception is nearly useless on it's own, which texture was it? What path did it have, what module was it being loaded etc. it's thrown too low to be useful, you can't catch it very high or you wont know which module threw it or have to poll them all and ask if any of them recently had a texture load cycle that got interrupted -> awesome...
If it was thrown even one level higher in method that loads all paths, it could include part of the path and then no matter where you caught it, even in main, you would know that red_splash.png must have been thrown from particle module, hero.png is from sprites module and adorable_katzilla.png is from PrettyKitty module, or you might throw different exceptions with different what() altogether for different modules. Non optional exceptions are annoying, require boilerplate to work around and might become very slight(but still existent..) performance hit.
Back to C++ gamedev with SFML in May 2023

MorleyDev

  • Full Member
  • ***
  • Posts: 219
  • "It is not enough for code to work."
    • View Profile
    • http://www.morleydev.co.uk/
Re: Exceptions in SFML 3 ?
« Reply #7 on: October 12, 2013, 12:47:42 pm »
To use C# as an example, Parse vs TryParse. Parse throws on failure to Parse, TryParse returns true/false. Is this problem not solved by creating tryX functions to create functions for whom failure is not exceptional.

For loadFile vs tryLoadFile,  that actually feels significantly better to me.  loadFile says "I want to load this file" and failure is an exceptional scenario, and with tryLoadFile it's saying "I want to load this file but not guarantee it will work".

Exceptions are for exceptional situations that deviate from the expected control flow, tryLoadFile is explicitly including both success and failure in it's expected control flow, whilst loadFile failing to load the file is an exceptional situation.
« Last Edit: October 12, 2013, 12:52:52 pm by MorleyDev »
UnitTest11 - A unit testing library in C++ written to take advantage of C++11.

All code is guilty until proven innocent, unworthy until tested, and pointless without singular and well-defined purpose.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Exceptions in SFML 3 ?
« Reply #8 on: October 12, 2013, 01:46:37 pm »
To use C# as an example, Parse vs TryParse. Parse throws on failure to Parse, TryParse returns true/false. Is this problem not solved by creating tryX functions to create functions for whom failure is not exceptional.

For loadFile vs tryLoadFile,  that actually feels significantly better to me.  loadFile says "I want to load this file" and failure is an exceptional scenario, and with tryLoadFile it's saying "I want to load this file but not guarantee it will work".

Exceptions are for exceptional situations that deviate from the expected control flow, tryLoadFile is explicitly including both success and failure in it's expected control flow, whilst loadFile failing to load the file is an exceptional situation.
Describing the different interfaces through their function names is a relic from C times. In C++ (and C#) overloading was introduced to solve that problem. Having TryX functions instead of just providing a transparent overload does not conform with modern C++ style. Some people argue that it might not be clear what to pass to the function without reading the documentation. In that case, the interface is bad anyway and should be changed to something self-explanatory.

There are 2 scenarios:
#1: The user expects success and would exit with a fatal error anyway if the current interface returned something that denotes failure. For this, exceptions are just right and even easier to use than the current interface:
// Old way:
if( !loadFile( name ) ) {
        std::cerr << "Fatal error: Couldn't load file " << name;
        return 1; // Exit the application
}

// New way:
loadFile( name );
// If this fails, and the user doesn't catch the exception, the application will close with a hopefully descriptive error message.
// terminate called after throwing an instance of 'sf::file_load_failed'
// If you wanted, you could catch the exception at the end of your main() and make it print more useful information via .what() and rethrow it.
 
#2: The user cares about whether the file was loaded or not and execution of the application can continue albeit in a slightly different manner. This would be the case if the if-block was triggered with the current interface:
// Old way:
if( !loadFile( name ) ) {
        std::cerr << "Couldn't load file " << name << ", oh well... now you won't be able to view its contents.";
}
// Continue execution...

// New way:
sf::Error error;
loadFile( name, error ); // optionally declared noexcept, error is passed by reference
// The user can check for the error because they might do something differently based on it.
if( error == sf::Error::FileLoadFailed ) {
        std::cerr << "Couldn't load file " << name << ", oh well... now you won't be able to view its contents.";
}
// Continue execution...
 
This is already done in many C++ libraries. I personally use a mix of both. Sometimes you need scenario 1 functionality and sometimes you need scenario 2 functionality, but at least there is a way to specify which you want. This also frees up the return value for more "useful purposes".
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

MorleyDev

  • Full Member
  • ***
  • Posts: 219
  • "It is not enough for code to work."
    • View Profile
    • http://www.morleydev.co.uk/
Re: Exceptions in SFML 3 ?
« Reply #9 on: October 13, 2013, 12:42:47 am »
Except here my argument is these are not the same function, that they have different responsibilities and as such deserve different names. Overloads exist to present a common responsibility with multiple interfaces, not to present multiple responsibilities with a common interface.

tryLoad says failure can be expected so failure is not exceptional, which is discrete from load which says success should be guaranteed and thus failure is exceptional.

I will grant the error pattern you present may be established in C++ for handling error conditions in situations where errors are exceptional but users do not want to use exceptions for various reasons. But here I present an alternative which is to create a tryLoad that acknowledges failure as not exceptional, and put forth that as a cleaner solution that more closely matches the needs of the user.

That is my argument, although I'll grant it hinges on whether or not you think there should be a version of loading that doesn't regard failure as exceptional.

As an aside, can I say I do love that these intellectual design discussions crop up in the SFML community with some regularity. It's nice when sometimes it can seem there are so few programmers out there who value thinking about this kind of thing. The debate is always fascinating.

And to be honest I have other issues with the current Texture/Image/Shader loading, namely I disagree that any of those should know how to load themselves from the file system, that it should not be their responsibility.
« Last Edit: October 13, 2013, 02:56:16 pm by MorleyDev »
UnitTest11 - A unit testing library in C++ written to take advantage of C++11.

All code is guilty until proven innocent, unworthy until tested, and pointless without singular and well-defined purpose.

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #10 on: October 13, 2013, 03:13:35 am »
Well, if we're gonna keep having this debate in this thread...

I definitely disagree with the idea that classes shouldn't know how to load themselves.  That kind of simplicity is what makes SFML so incredibly useful.  I think you could make a case for a non-member function to do all of that like sf::loadFromFile(sf::Texture& tex, sf::String filename).  On the other hand, where would you put the overloads of this function?

It seems like the main point we all agree on is that loadFromFile at the very least should have an option to return an error instead of throw an exception.  Can anyone think of any other functions where we can make a strong case for two sets of expectations?

Regarding how the option would be implemented, I agree with binary that it should be a transparent overload, and that the exception-on-failure option should be the default single-parameter overload.  I'm not sure what I'd want the second parameter to be though, and I think that would partly depend on whether there are enough other functions with this option to warrant designing an sf::Error struct (union?) to act as the extra parameter in these cases.

MorleyDev

  • Full Member
  • ***
  • Posts: 219
  • "It is not enough for code to work."
    • View Profile
    • http://www.morleydev.co.uk/
Re: Exceptions in SFML 3 ?
« Reply #11 on: October 13, 2013, 02:49:53 pm »
Well in code they don't actually know it, but they appear to do so from the API point of view. That, to me, is a big hint that the responsibility is in the wrong place: SFML Image has feature envy of the ImageLoader. I'd rather an ImageLoader be an explicit class I have to initialise and use. To me, that is simpler. I definitely think it's easier to use and test code that separates use from construction like that.

I can't think of anything else in SFML that has two different expectations like loading does. Creating a texture, maybe? It's the things that do something and return true or false as a way of reporting success or failure, which are rare in SFML anyway. With an exception of pollEvent., of course.
« Last Edit: October 13, 2013, 02:54:32 pm by MorleyDev »
UnitTest11 - A unit testing library in C++ written to take advantage of C++11.

All code is guilty until proven innocent, unworthy until tested, and pointless without singular and well-defined purpose.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Exceptions in SFML 3 ?
« Reply #12 on: October 13, 2013, 02:59:24 pm »
I'd rather an ImageLoader be an explicit class I have to initialise and use. To me, that is simpler. I definitely think it's easier to use and test code that separates use from construction like that.
I disagree, this adds user-unfriendly complexity without real advantages, apart from theoretically "nicer" OOD.

Even this one is questionable: The SFML resource classes already have the knowledge about low-level resource handling and access to other libraries, be it for loading, preparing, playing or unloading. It doesn't make sense to split half of this functionality into a separate class.

I can't think of anything else in SFML that has two different expectations like loading does.
Maybe connecting sockets and sending/receiving packets? This is however more complex because of two operation modes (blocking and non-blocking).

By the way, it would be nice if Laurent could split the discussion :)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: Exceptions in SFML 3 ?
« Reply #13 on: October 13, 2013, 03:04:54 pm »
I do not at all see why that would be any simpler, and here's the fake code I'm imagining to prove it:

Current:
sf::Texture tex;
if(!tex.loadFromFile("Mario.png"))
  throw my_exception("Couldn't find Mario.png");
 

Your proposal (?):
sf::Texture tex;
sf::TextureLoader texl;
if(!texl.loadFromFile("Mario.png"))
  throw my_exception("Couldn't find Mario.png");
tex = texl.extractTexture();
 

Off the top of my head I have no idea what other features an sf::TextureLoader might have that aren't already in sf::Texture.

To put this in super-generalized theoretical terms (or at least the dialect of them that I'm using in my head right now): image loading is a task.  It's not an object to be acted upon, but a task to be performed.  One task means one function.  The object that image loading acts upon is the object the loaded image is used in, be it an sf::Image or an sf::Texture.  Thus, image loading should be done by a function that acts on sf::Image or sf::Texture.  At that point it's just a question of member or non-member function.  For sf::TextureLoader to ever possibly be a Good Thing(TM), we need to come up with some functionality it can have other than a slightly more complicated interface to sf::Texture.loadFromFile with the exact same behavior.

Now present the fake code you're imagining so we can dig into the details. =)

Quote
By the way, it would be nice if Laurent could split the discussion :)

Yeah, at first I was trying not to post in this too many times for fear of annoying someone, but since other people kept posting I figured he thought it was okay.
« Last Edit: October 13, 2013, 03:07:15 pm by Ixrec »

MorleyDev

  • Full Member
  • ***
  • Posts: 219
  • "It is not enough for code to work."
    • View Profile
    • http://www.morleydev.co.uk/
Re: Exceptions in SFML 3 ?
« Reply #14 on: October 13, 2013, 03:16:19 pm »
I'm cool with this going into a new thread if people want. I mostly just said the whole "image loading" thing as a way of highlighting my attitude towards design and programming, namely I prefer very discrete responsibilities in code. I'm also big on testability, for me simpler code is code that's easier write unit tests with and for. Code, frankly, cannot be simple without being testable for me.

Essentially, it appears things like create and loadFromFile were made because, due to the fact they could fail, they're reasonably complex and SFML atm doesn't use exceptions, people didn't want them in the constructors. But that, to me, doesn't mean move them to a member function. It means move them out the class.

If you look at the implementation of Texture.loadFromFile, it just calls through to sf::Image. At present every usage of it could be inlined and since it's pretty much just a helper done to hide the need of sf::Image it literally would inline to:

Code: [Select]
sf::Image image;
auto success = image.loadFromFile("filename") && texture.loadFromImage(image);

It's a helper function, nothing more. My preferred usage would be more like:

Code: [Select]
sf::ImageLoader imageLoader;
sf::Image image = imageLoader.loadFile("mario.png");
sf::Texture texture(image);

And loadFile throws on failure. Or does whatever the above debate will yield, Exceptions with an overload that takes sf::Error seems to be the preference. And I'll admit I do not mind that approach.

Really this is more about the whole self-mutating nature of image/texture, I find it preferable for images be created in their desired state and changing the image requires reconstructing the object.

But I always just create my own factories that look a lot like the above for me anyway, so it's a minor design nibble in the end that doesn't get in the way and I didn't intend for my aside comment to distract from the main topic. For that I apologise.
« Last Edit: October 13, 2013, 04:20:54 pm by MorleyDev »
UnitTest11 - A unit testing library in C++ written to take advantage of C++11.

All code is guilty until proven innocent, unworthy until tested, and pointless without singular and well-defined purpose.