C Block support for functions that normally come in begin/end pairs. #97

Closed
wants to merge 2 commits into
from

Conversation

Projects
None yet
3 participants
Contributor

erisdev commented Aug 12, 2011

This basically amounts to syntax sugar—makes the C++ interface for these sorts of operations more like the Ruby interface. It only works where blocks are available, of course. Observe.

graphics().transform(translate(-scrollX, -scrollY), ^{
    allTheThings.draw();
    player.draw();
});

graphics().gl(^{
    glFantastic3DEffects();
    glDoMagic();
    glApplyTrendyBloomFilter();
});

I'm not sure this support is compiled in by default on OS X since Gosu is using the 10.4 SDK, but it would be a nice bonus for people who don't mind compiling Gosu themselves. I'm pretty sure Clang supports Blocks on other platforms, too, so I think they are here to stay.

Owner

jlnr commented Aug 15, 2011

Oh, shiny! I hadn't had time to check out C blocks, but I read a really in-depth technical book chapter about them a long time ago. Impressive stuff, except that they are not on the heap by default. I.e., for (int i = 0; i < 5; ++i) blocks[i] = ^{ puts(strings[i]); } will actually set all five blocks to output the same string...unintuitive for such a high-level feature. Oh, anyway... :)

I think it looks great and it makes stuff like enqueueGL() much nicer. I should rename that to gl to resemble the Ruby interface. But how about compatibility with C++0x lambdas? They have an unknown type, but can be assigned to std::function<…>. Do you know if I can get away with using std::function<…> as the arguments and just passing blocks? I have found this SO link, but it melts my brain a bit:

http://stackoverflow.com/questions/5438613/why-cant-i-use-a-boostfunction-in-an-objective-c-block

Contributor

erisdev commented Aug 15, 2011

Huh, I didn't actually know about C++ lambdas. Those look pretty nice.

I did a little research, and it turns out that you "can" use C blocks with std::tr1::function objects (just like you can use functors and function pointers), but it's not safe to pass that function object around if it's going to outlive the scope where the block was created. You'd need to specialise the template so that it calls Block_copy() (or Block_retain()—I'm not sure which would be recommended) and Block_release() in the appropriate places.

As it turns out, blocks appear to be Objective-C objects with Cocoa memory management semantics—even without the Objective-C runtime, apparently—so you can actually retain/release/autorelease them in Cocoa code. That's pretty cool. I wonder whether the specification says anything about the implementation, or if that's just a hack that Apple supports on their platforms.

Contributor

erisdev commented Aug 15, 2011

FWIW, now that I know about C++ lambdas, I think it would be more appropriate to just use those rather than C blocks. If blocks are needed for, say, a Cocoa wrapper, a functor class could be written to wrap them.

(errr, oops, didn't mean to close the pull request)

@erisdev erisdev closed this Aug 15, 2011

@erisdev erisdev reopened this Aug 15, 2011

Owner

jlnr commented Aug 15, 2011

Hmmm, that sounds like a reasonable specialization that the standard library of a capable compiler should supply. As far as I can tell, the limitation doesn't matter to Gosu's use case though (slap me if I misunderstood it). So if Gosu sticks with ´std::tr1::function`, just allows it in more places, both Blocks and C++0x lambda users will be happy.

I also wonder if I should seize the opportunity and move those functions from Graphics into the Gosu namespace to reduce the need for Window references. Oh, so many decisions! I shall try to fix the OpenAL issues on Windows first so I can move on to Gosu 0.8 :)

Contributor

erisdev commented Aug 15, 2011

As far as I can tell, the limitation doesn't matter to Gosu's use case though

You're right—it shouldn't be a problem for most of the use cases in Gosu. The block-accepting functions basically push, call and pop, so the block isn't going to be called outside of its original scope. The only instance where the block would need to be copied is scheduleGL.

move those functions from Graphics into the Gosu namespace

Yes, please. Ruby Gosu, too? :D

I'd also suggest overloading gl instead of having two separate functions, but I suppose this is more of an aesthetic matter:

void gl(double z, std::tr1::function); // adds to the draw op queue
void gl(std::tr1::function);           // flushes and executes immediately

And finally, do you know a good resource for figuring out this newfangled tr1 stuff? I tried looking at the GNU reference docs and all the underscores and terse parameter names broke my brain. C:

Owner

jlnr commented Aug 15, 2011

Hmmm, flushing GL and executing stuff directly is kind of weird anyway. It just used to be the easiest thing to add at the time. Maybe I should just deprecate it for good?
And yes, Ruby/Gosu is going to de-emphasize the window too. I guess it won't be a great step ahead for small examples though, unless you actually include Gosu.

And that part about scheduleGL is right. Mhm, pretty ugly :( Oh well, if it is exactly 1 case, and adding __block (as seen on StackOverflow) fixes the problem, that would be worth a note in the documentation.

tr1: I think you should skip that and jump straight to C++0x, which has a nice Wikipedia page. I don't even get why they first included stuff from boost and C99 into std::tr1:: (C++ TR1/"2003") and now copied it into std:: (C++0x). I would use the new std:: stuff directly, but the compilers aren't nearly fast enough for that. :(

Contributor

erisdev commented Aug 15, 2011

and adding __block (as seen on StackOverflow) fixes the problem

I think you may have misunderstood after all--unless I'm misunderstanding you on the issue with blocks. I need to sleep pretty badly, but I can try to explain myself better tomorrow.

Basically, the block itself needs to be copied off the stack and onto the heap if it's going to be called after the function where it was created returns. It's pretty hairy. Like so hairy it has a beard down to its knees. I'll say more tomorrow!

Owner

jlnr commented Aug 15, 2011

Oh, I only misunderstood the StackOverflow posting, not the actual mechanism. I thought adding __block to this line

__block boost::function<void()> f = boost::bind(&foo);

was some bizarro magic way of saying that f needs to copy the block foo onto the heap. But I think the syntax is actually sane and it is more like

boost::function<void()> f = Block_copy(foo);

Then of course /this/ should be added to the Gosu docs for gl() :)

Oh wait, and it needs to be freed. Recommending use of the GC only for this sounds a bit overkill (and hardly portable). I guess you could still do it with a C++0x one-liner (shared_ptr with custom deleter?), but maybe Gosu should just use a tiny helper struct and overload enqueueGL for passing blocks.

Contributor

erisdev commented Aug 15, 2011

Looks like you've got a handle on it after all, then.

I suppose for now letting the caller handle memory management for the block is reasonable. It's only for that one call (certainly not a core feature—more of a nice extra) and C++ lambdas are also an option, so the only instance where blocks would be particularly useful is probably a Cocoa wrapper, where you could just have the wrapper object retain and release the block.

Owner

jlnr commented Aug 15, 2011

Well, I am not so sure if C++0x lambdas are really an option already. Blocks are probably are much more stable choice right now if you are doing a Mac/iOS game. But if Gosu accepts a function<>, then in the worst and most portable case, people could just pass a named function or method just as they already do for enqueueGL.

My old plan to get rid of the fugly begin/end pairs was to use a wrapper struct, say GLContext that would call endGL in its destructor. That would be very C++98-esque, where destructors are used for everything. But it would also be completely bizarre if anyone would try to allocate those things on the heap :)

Contributor

erisdev commented Aug 16, 2011

Would it be reasonable (or feasible) to create a specialised instantiation of function just for, say, a void (^)(void) block? I'm finding the more I learn about C++, the less I really understand. :D

Owner

jlnr commented Aug 16, 2011

You should be able to even specialize function for all blocks at once if you just throw more template magic at it. But it's illegal to mess around in std:: :(

Now that I think about it, ARC should be able to handle it. %) C++ will probably naively store a reference (magic ARC pointer) to the block in some struct, and the ObjC part of the compiler will add the retain/release calls as necessary. Oh man...

Contributor

erisdev commented Aug 16, 2011

Nah, ARC is unfortunately not as magical as is sounds. It actually does static analysis of your code and then inserts retain and release calls according to standard Cocoa conventions. No magic pointer wrappers; more of an advanced in-memory preprocessor. :C

Owner

jlnr commented Aug 16, 2011

Hmmm, but is there a difference between me passing a pointer to a block around (and clang doing its thing) and C++ passing it around in anonymous, compiler-generated lambda structs? I have no idea. I would have expected that it just treats pointers to NSAnything just like C++ smart pointers work, i.e. that it inserts retain and release whenever you assign them, or when they go out of scope, respectively. And then it would use static analysis to get rid of superfluous calls. But I haven't yet finished watching the iTunes U podcasts about ARC & friends, will do that ASAP.

Contributor

erisdev commented Aug 16, 2011

Yeah—unfortunately, one of the limitations of ARC is that you can't store pointers to Objective-C objects in non-Objective-C locations. When you compile a class with ARC enabled, you're basically telling the compiler that objects of that class should automatically retain and release references that it holds so that, for instance,

- (void)setThing:(NSString *)newThing
{
    thing = newThing;
}

- (NSString *)thing
{
    return thing;
}

might compile to something more like

- (void)setThing:(NSString *)newThing
{
    [newThing retain];

    if ( thing != nil )
        [thing autorelease];

    thing = newThing;
}

- (NSString *)thing
{
    return [[thing retain] autorelease];
}

So, unfortunately, ARC isn't useful when you're interacting with an external library that knows nothing of Cocoa.

Note also that my example above is a bit silly, since the proper way to create simple accessors like this is to use a @property declaration and then @synthesize them in the implementation file, but it serves as a decent example anyway. :D

@jlnr jlnr added this to the 1.0 milestone Dec 30, 2016

@jlnr jlnr self-assigned this Dec 30, 2016

Contributor

pmer commented Apr 21, 2017

Would C++ lambdas be more viable now in 2017?

Owner

jlnr commented Apr 21, 2017

Yep, std::function arguments should work just fine for C++ lambdas. Now we can even use std::function without the ::tr1. Now might actually be a good time to add this, as this is a medium-sized C++ interface change, and I want to bump the version to 0.12 when I release MP3 support anyway (due to changed dependencies on Linux).

Owner

jlnr commented Apr 21, 2017

I've just given it a try and it seems really straightforward, except for the Graphics::end_gl function maybe. I'll probably send a PR and close this one soon.

Owner

jlnr commented Apr 22, 2017

Thanks for the original PR @erisdev and for bumping this @pmer, I've pushed a std::function-based version at #399

@jlnr jlnr closed this Apr 22, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment