Skip to content
This repository

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

Open
wants to merge 2 commits into from

2 participants

Eris Julian Raschke
Eris
Collaborator

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.

Julian Raschke
Owner
jlnr commented August 14, 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

Eris
Collaborator

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.

Eris
Collaborator

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)

Eris erisdiscord closed this August 14, 2011
Eris erisdiscord reopened this August 14, 2011
Julian Raschke
Owner
jlnr commented August 14, 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 :)

Eris
Collaborator

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:

Julian Raschke
Owner
jlnr commented August 14, 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. :(

Eris
Collaborator

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!

Julian Raschke
Owner
jlnr commented August 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.

Eris
Collaborator

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.

Julian Raschke
Owner
jlnr commented August 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 :)

Eris
Collaborator

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

Julian Raschke
Owner
jlnr commented August 15, 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...

Eris
Collaborator

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

Julian Raschke
Owner
jlnr commented August 15, 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.

Eris
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
20  Gosu/Graphics.hpp
@@ -29,6 +29,10 @@ namespace Gosu
29 29
     Transform scale(double factor);
30 30
     Transform scale(double factorX, double factorY, double fromX = 0, double fromY = 0);
31 31
     
  32
+#ifdef __BLOCKS__
  33
+    typedef void (^GraphicsBlock)(void);
  34
+#endif
  35
+    
32 36
     //! Serves as the target of all drawing and provides primitive drawing
33 37
     //! functionality.
34 38
     //! Usually created internally by Gosu::Window.
@@ -110,6 +114,22 @@ namespace Gosu
110 114
         std::auto_ptr<ImageData> createImage(const Bitmap& src,
111 115
             unsigned srcX, unsigned srcY, unsigned srcWidth, unsigned srcHeight,
112 116
             unsigned borderFlags);
  117
+
  118
+#ifdef __BLOCKS__
  119
+        //! Executes the block in a clean OpenGL environment, resetting Gosu
  120
+        //! to its default rendering state after the block returns.
  121
+        void gl(GraphicsBlock block);
  122
+        
  123
+        //! Executes the block with all drawing clipped to a specified rectangle.
  124
+        void clipTo(double x, double y, double width, double height, GraphicsBlock block);
  125
+        
  126
+        //! Executes the block, applying a specified transformation to all
  127
+        //! drawing operations.
  128
+        void transform(const Transform& transform, GraphicsBlock block);
  129
+        
  130
+        //! Records the block as a macro, returning the result as a drawable object.
  131
+        std::auto_ptr<Gosu::ImageData> record(GraphicsBlock block);
  132
+#endif
113 133
     };
114 134
 }
115 135
 
32  GosuImpl/Graphics/Graphics.cpp
@@ -473,3 +473,35 @@ std::auto_ptr<Gosu::ImageData> Gosu::Graphics::createImage(
473 473
 
474 474
     return data;
475 475
 }
  476
+
  477
+#ifdef __BLOCKS__
  478
+
  479
+void Gosu::Graphics::gl(GraphicsBlock block)
  480
+{
  481
+    beginGL();
  482
+    block();
  483
+    endGL();
  484
+}
  485
+
  486
+void Gosu::Graphics::clipTo(double x, double y, double width, double height, GraphicsBlock block)
  487
+{
  488
+    beginClipping(x, y, width, height);
  489
+    block();
  490
+    endClipping();
  491
+}
  492
+
  493
+void Gosu::Graphics::transform(const Transform& transform, GraphicsBlock block)
  494
+{
  495
+    pushTransform(transform);
  496
+    block();
  497
+    popTransform();
  498
+}
  499
+
  500
+std::auto_ptr<Gosu::ImageData> Gosu::Graphics::record(GraphicsBlock block)
  501
+{
  502
+    beginRecording();
  503
+    block();
  504
+    return endRecording();
  505
+}
  506
+
  507
+#endif
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.