Friday, March 14, 2008
Dear iPhone SDK haters,
Nobody is forcing you to develop for the iPhone. If you don't like it, by all means, go develop for 'Android' and be sure to let me know how that works out for you.
Tuesday, March 11, 2008
A little FFI goes a long way
As my two faithful readers have surely noted, one of my little projects is a Leopard-specific GUI for R that tries to leverage as many of the new features as possible. As it happens, one of those features is Garbage Collection, which I can't use because of the way the R framework is built. Perhaps I'll be able to use Simon's method for building self-contained R applications with the framework built in the appropriate way.
What I'd really like to talk about though is the fact that having PyObjC and whatever the Ruby bridge is called means that I can be positive that a copy of libffi is available on every Leopard system. This let me finally build something I've wanted for a long time: an elegant C<->Cocoa thunk for the R function pointers. To wit:
So, how do we do it? Well, first we need a little bit of context to keep a binding between a function pointer and a specific object/method combination. We also keep the types of arguments around as well as the return type. If we were doing a complete libffi bridge we would introspect this from the class at runtime rather than the static method I use here. In the case of R this would be overkill since the R function pointers are very much fixed entities. In any case, here's the little structure I use:
Next, we need to write a little function to use as a closure and move the C function call to the ObjC function call. All libffi closure handlers look have the same function declaration:
First we build a place to hold our arguments
and simply transfer the arguments accross, making sure that the first two are the target object and target selector (Obj-C methods have two implicit arguments):
Then all we do is use ffi_call to make a call. Note that we use the NSAutoreleasePool method, which wouldn't be necessary if we could enable garbage collection:
Next we need a little function to build an appropriate closure function to use as the R function pointer:
We use vargs so that we can define the argument lists inline. First, we allocate our context structure and transfer the argument types
Next we define the two ffi_cifs. The first one is the ffi_cif of the Obj-C method call and the second is the one for the function pointer we're trying to create. Finally, we create the closure and return it
and that's pretty much all there is to it. There are two minor caveats, the first is that anything that employs method swizzling on this class is likely to fail since we cache the function pointer at bind time. Fortunately, swizzling is relatively rare these days so it generally doesn't happen. It may also technically be slower than the hand-coded method, but there are a couple of areas where we could probably speed up the thunking operation---the allocation of
What I'd really like to talk about though is the fact that having PyObjC and whatever the Ruby bridge is called means that I can be positive that a copy of libffi is available on every Leopard system. This let me finally build something I've wanted for a long time: an elegant C<->Cocoa thunk for the R function pointers. To wit:
ptr_R_WriteConsoleEx = ffi_bind(self,@selector(writeConsole:length:type:),&ffi_type_void,
4,&ffi_type_pointer,&ffi_type_sint,&ffi_type_sint);
So, how do we do it? Well, first we need a little bit of context to keep a binding between a function pointer and a specific object/method combination. We also keep the types of arguments around as well as the return type. If we were doing a complete libffi bridge we would introspect this from the class at runtime rather than the static method I use here. In the case of R this would be overkill since the R function pointers are very much fixed entities. In any case, here's the little structure I use:
typedef struct {
id obj;
SEL sel;
IMP fn;
int nargs;
ffi_type *retval;
ffi_cif *cif;
ffi_type *types[0];
} ffi_call_struct;
Next, we need to write a little function to use as a closure and move the C function call to the ObjC function call. All libffi closure handlers look have the same function declaration:
void handler(ffi_cif *cif,void *retval,void *args[],void *user_data) {
First we build a place to hold our arguments
int i;
ffi_call_struct *call = (ffi_call_struct*)user_data;
void **values = (void**)malloc(sizeof(void*)*(call->nargs+2));
and simply transfer the arguments accross, making sure that the first two are the target object and target selector (Obj-C methods have two implicit arguments):
for(i=0;inargs;i++) { values[2+i] = args[i]; }
values[0] = &(call->obj);
values[1] = &(call->sel);
Then all we do is use ffi_call to make a call. Note that we use the NSAutoreleasePool method, which wouldn't be necessary if we could enable garbage collection:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ffi_call(call->cif,(void (*)(void))call->fn,retval,values);
[pool release];
free(values);
}
Next we need a little function to build an appropriate closure function to use as the R function pointer:
void *ffi_bind(id obj,SEL selector,ffi_type *retval,int nargs,...) {
int i;
ffi_cif *cif;
ffi_closure *closure;
va_list ap;
We use vargs so that we can define the argument lists inline. First, we allocate our context structure and transfer the argument types
//Allocate the structure and copy the call information into the structure.
ffi_call_struct *call = (ffi_call_struct*)malloc(sizeof(ffi_call_struct)+(nargs+2)*sizeof(ffi_type*));
call->obj = obj;
call->sel = selector;
call->fn = [obj methodForSelector:selector];
call->retval = retval;
call->nargs = nargs;
call->cif = (ffi_cif*)malloc(sizeof(ffi_cif));
cif = (ffi_cif*)malloc(sizeof(ffi_cif));
call->types[0] = &ffi_type_pointer;
call->types[1] = &ffi_type_pointer;
va_start(ap,nargs);
for(i=0;itypes[2+i] = va_arg(ap,ffi_type*);
Next we define the two ffi_cifs. The first one is the ffi_cif of the Obj-C method call and the second is the one for the function pointer we're trying to create. Finally, we create the closure and return it
ffi_prep_cif(call->cif,FFI_DEFAULT_ABI,nargs+2,call->retval,call->types); //The CIF for the ObjC Method
ffi_prep_cif(cif,FFI_DEFAULT_ABI,nargs,call->retval,&(call->types[2])); //The CIF for the function call
closure = (ffi_closure*)malloc(sizeof(ffi_closure));
ffi_prep_closure(closure,cif,handler,call);
return closure;
}
and that's pretty much all there is to it. There are two minor caveats, the first is that anything that employs method swizzling on this class is likely to fail since we cache the function pointer at bind time. Fortunately, swizzling is relatively rare these days so it generally doesn't happen. It may also technically be slower than the hand-coded method, but there are a couple of areas where we could probably speed up the thunking operation---the allocation of
valuesin particular could be moved to the context object.
Thursday, March 6, 2008
And the bloggers chime in...
So, the iPhone SDK was announced (good luck with actually downloading it) and I'm looking forward to playing around with it (no, I'm not going to try to get R running. Let's face it, the iPhone UI is more like, say, Data Desk than anything else). I am getting annoyed with assorted bloggers and pundits bitching about perceived shortcomings.
First, the 70/30 split. That seems to be the biggest complaint. In the words of Dr. Evil, "quite typical, really." Jobs is probably right when he says that it's just enough to cover expenses. Credit card processing ain't free, kids. They might see a little net revenue, but it's in the noise relative to the rest of their business. Basically, apps are a way to sell iPhones, and it'll work.
I also saw someone complain about not being able to change the iPhone UI L&F. Well, yeah. Duh. Apple has seen X11 and knows exactly what happens when you allow that sort of control. Basically, rip-offs of the Windows 95 UI (No, don't expect me to be impressed that you can map Windows 95 on to the sides of a cube and spin it around. At the end of the day you've still got Windows 95 with uglier controls).
Speaking of UI and complete non sequiturs (well, not completely since Salesforce presented in the iPhone thing). I ran across an O'Reilly book this evening at Barnes & Noble called "Designing Dashboards," except that the title was longer. For statisticians with at least a mild interest in presenting results there's nothing there that comes as a surprise (and several areas that could have used some help, color palette selection for example). Clearly the author has read Tufte's books (sparklines even made an appearance). Mostly, though, I'm struck by how absolutely horrible the types of dashboard produced by things like Business Objects and Salesforce (there are others, but these are really popular). The outputs are very expensive in terms of real estate (lots of round things, pie charts and dials and such. Usually in some godawful 3d rendering) and deliver very little, usually a single number. Even when they become more traditional/less junky, the little things really start to stand out. There's a lot of "stop light color" usage, which seems like a good idea but really just induces fatigue (I tried this recently in my own workflow tool and could only stand to use the tool for about 10 minutes. The same dimension is now represented by a much more subtle change in glyph size, which is surprisingly easy to assess at a glance). Even when they move away from stop-light colors there's no rhyme or reason to the color choices nor any apparent internal consistency.
Anyway, I'll probably write more on what I'm learning about visual display of complex information from the workflow tool I've been developing as a place to hang my modeling hat for the last couple of months. I've got a stable core set of users and I use the tool myself literally daily (usually the development version which varies somewhat from the 'production' version). I've also got some things to say about R and databases and workflow in that environment as well.
First, the 70/30 split. That seems to be the biggest complaint. In the words of Dr. Evil, "quite typical, really." Jobs is probably right when he says that it's just enough to cover expenses. Credit card processing ain't free, kids. They might see a little net revenue, but it's in the noise relative to the rest of their business. Basically, apps are a way to sell iPhones, and it'll work.
I also saw someone complain about not being able to change the iPhone UI L&F. Well, yeah. Duh. Apple has seen X11 and knows exactly what happens when you allow that sort of control. Basically, rip-offs of the Windows 95 UI (No, don't expect me to be impressed that you can map Windows 95 on to the sides of a cube and spin it around. At the end of the day you've still got Windows 95 with uglier controls).
Speaking of UI and complete non sequiturs (well, not completely since Salesforce presented in the iPhone thing). I ran across an O'Reilly book this evening at Barnes & Noble called "Designing Dashboards," except that the title was longer. For statisticians with at least a mild interest in presenting results there's nothing there that comes as a surprise (and several areas that could have used some help, color palette selection for example). Clearly the author has read Tufte's books (sparklines even made an appearance). Mostly, though, I'm struck by how absolutely horrible the types of dashboard produced by things like Business Objects and Salesforce (there are others, but these are really popular). The outputs are very expensive in terms of real estate (lots of round things, pie charts and dials and such. Usually in some godawful 3d rendering) and deliver very little, usually a single number. Even when they become more traditional/less junky, the little things really start to stand out. There's a lot of "stop light color" usage, which seems like a good idea but really just induces fatigue (I tried this recently in my own workflow tool and could only stand to use the tool for about 10 minutes. The same dimension is now represented by a much more subtle change in glyph size, which is surprisingly easy to assess at a glance). Even when they move away from stop-light colors there's no rhyme or reason to the color choices nor any apparent internal consistency.
Anyway, I'll probably write more on what I'm learning about visual display of complex information from the workflow tool I've been developing as a place to hang my modeling hat for the last couple of months. I've got a stable core set of users and I use the tool myself literally daily (usually the development version which varies somewhat from the 'production' version). I've also got some things to say about R and databases and workflow in that environment as well.
Wednesday, January 30, 2008
Long time, no post.
So, a comment to my Cover Flow post yesterday reminded me that I haven't posted anything here in quite some time despite making a lot of progress on an R GUI over the Summer and the release of Leopard at the end of October. I have an excuse though! See, in mid-October I left academia and started working for a company in San Francisco called AdBrite. I'm pretty happy with things so far, I get to do statistics and program basically as much as I want. There were a bunch of factors that contributed to the switch from academics, most of which I'm not going to discuss on a public blog, though I'm happy to correspond via email. Long story short, AdBrite made me an offer I couldn't refuse (and it had nothing to do with the Nolan Lab, Garry and all the folks in the lab are great). The whole NIH obligation thing could become a problem (this is an area where cross discipline researchers really get screwed), but I'll deal with it as it comes, I try to think of it as a really ill-advised student loan.
Anyway, this means I haven't had much time for the R Mac GUI in the last several months as most of the work I've been doing has been more server-side rather than client-side so I haven't seen much of the R GUI and when I do see it, it happens to be on a Windows box---we have SAS and SAS notably is not available for OS X. I suppose I could Parallels it, but I'd still be running Windows and the Win64 box was already here.
I'd like to get back to it, but we'll have to see. I think at the very least I'll get the code off my backup drive and put the source code up somewhere so it isn't lost to the ages. On the bright side, a lot of the graphics drawing code made it in the core R release for 2.7.0 and the API Simon chose to adopt is fairly similar so a bunch of the graphics code can actually be removed. Simon also put paging into the core graphics device, though it's not as fun as Cover Flow or the Image Browser version (though those are Leopard specific so he can't really put it into the core R).
I think there's some useful stuff in the Console implementation, particularly the clipboard support and the improved completion/hinting support. I like Simon's idea of having a built-in console with the ability to spawn external consoles at will, there are some things (RGL comes to mind) that didn't like the RExecServer setup very much.
I'll try to post more often and get that source up somewhere. :-)
Anyway, this means I haven't had much time for the R Mac GUI in the last several months as most of the work I've been doing has been more server-side rather than client-side so I haven't seen much of the R GUI and when I do see it, it happens to be on a Windows box---we have SAS and SAS notably is not available for OS X. I suppose I could Parallels it, but I'd still be running Windows and the Win64 box was already here.
I'd like to get back to it, but we'll have to see. I think at the very least I'll get the code off my backup drive and put the source code up somewhere so it isn't lost to the ages. On the bright side, a lot of the graphics drawing code made it in the core R release for 2.7.0 and the API Simon chose to adopt is fairly similar so a bunch of the graphics code can actually be removed. Simon also put paging into the core graphics device, though it's not as fun as Cover Flow or the Image Browser version (though those are Leopard specific so he can't really put it into the core R).
I think there's some useful stuff in the Console implementation, particularly the clipboard support and the improved completion/hinting support. I like Simon's idea of having a built-in console with the ability to spawn external consoles at will, there are some things (RGL comes to mind) that didn't like the RExecServer setup very much.
I'll try to post more often and get that source up somewhere. :-)
Friday, October 5, 2007
R 2.7.0 Quartz graphics device clipboard support
Tom Elliot recently posted a request to the R-SIG-Mac list looking for a way to programmatically put graphics onto the clipboard under OS X. Right now this is pretty hard, but I was inspired to patch R-devel's (the future 2.7.0) new Quartz device to allow for clipboard output.
After applying the patch, you can specify file="clipboard://" and when the device is closed (yes, you MUST close the graphics device to see output. It would probably be a good idea to write the file on a NewPage as well, come to think of it). Then you can wrap up a simple function to get copying of the current device to the clipboard (or you can simply open it directly).
I've attached the patch for the brave souls willing to try it out:
After applying the patch, you can specify file="clipboard://" and when the device is closed (yes, you MUST close the graphics device to see output. It would probably be a good idea to write the file on a NewPage as well, come to think of it). Then you can wrap up a simple function to get copying of the current device to the clipboard (or you can simply open it directly).
copy.to.clipboard = function(dpi=300) { dev.copy(device=quartz,type="png",file="clipboard://",dpi=dpi);dev.close(); }
I've attached the patch for the brave souls willing to try it out:
Index: qdBitmap.c
===================================================================
--- qdBitmap.c (revision 43081)
+++ qdBitmap.c (working copy)
@@ -49,16 +49,45 @@
/* On 10.4+ we can employ the CGImageDestination API to create a
variety of different bitmap formats */
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
- CFURLRef path = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(const UInt8*)qbd->path,strlen(qbd->path),FALSE);
- CFStringRef type = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->uti,strlen(qbd->uti),kCFStringEncodingUTF8,FALSE);
- CGImageDestinationRef dest = CGImageDestinationCreateWithURL(path,type,1,NULL);
- CGImageRef image = CGBitmapContextCreateImage(qbd->bitmap);
- CGImageDestinationAddImage(dest,image,NULL);
- CGImageDestinationFinalize(dest);
- CFRelease(image);
- CFRelease(dest);
- CFRelease(type);
+ CFStringRef pathString = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->path,strlen(qbd->path),kCFStringEncodingUTF8,FALSE);
+ CFURLRef path;
+ if(CFStringFind(pathString,CFSTR("://"),0).location != kCFNotFound) {
+ CFStringRef pathEscaped= CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,pathString,NULL,NULL,kCFStringEncodingUTF8);
+ path = CFURLCreateWithString(kCFAllocatorDefault,pathEscaped,NULL);
+ CFRelease(pathEscaped);
+ } else {
+ path = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(const UInt8*)qbd->path,strlen(qbd->path),FALSE);
+ }
+ CFRelease(pathString);
+
+ CFStringRef scheme = CFURLCopyScheme(path);
+ CFStringRef type = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->uti,strlen(qbd->uti),kCFStringEncodingUTF8,FALSE);
+ CGImageRef image = CGBitmapContextCreateImage(qbd->bitmap);
+ if(CFStringCompare(scheme,CFSTR("file"),0) == 0) {
+ CGImageDestinationRef dest = CGImageDestinationCreateWithURL(path,type,1,NULL);
+ CGImageDestinationAddImage(dest,image,NULL);
+ CGImageDestinationFinalize(dest);
+ CFRelease(dest);
+ } else if(CFStringCompare(scheme,CFSTR("clipboard"),0) == 0) {
+ //Copy our image into data
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault,0);
+ CGImageDestinationRef dest = CGImageDestinationCreateWithData(data,type,1,NULL);
+ CGImageDestinationAddImage(dest,image,NULL);
+ CGImageDestinationFinalize(dest);
+ CFRelease(dest);
+ PasteboardRef pb = NULL;
+ if(noErr == PasteboardCreate(kPasteboardClipboard,&pb)) {
+ PasteboardClear(pb);
+ PasteboardSyncFlags syncFlags = PasteboardSynchronize(pb);
+ PasteboardPutItemFlavor(pb,(PasteboardItemID)1,type,data,0);
+ }
+ CFRelease(data);
+ } else
+ warning("Not a supported scheme, no image data written.");
+ CFRelease(scheme);
+ CFRelease(type);
CFRelease(path);
+ CFRelease(image);
#endif
}
/* Free ourselves */
Friday, September 21, 2007
Those of your tracking R-devel may have noticed...
...that there was recently a big change to the way the Quartz device works. Turns out, Simon and I had roughly the same idea about a Quartz 2D implementation of the Quartz device for reasons of speed and the combined result (I ran "patch" on my R-devel tree and then Simon did all of the hard work :-) ) is in there now. This means the RExecServer and other R GUIs can now all use the same drawing backend for a variety of sources. I believe, for people who don't want to use RExecServer, that Simon also put in a CGLayer-based Cocoa target that runs an event loop in much the same way as RExecServer to give you interactive graphics natively in v2.7 (I'm guessing, since the feature freeze has passed). It also means native access to all of the Quartz 2D bitmap generation goodness and a lot more speed for ALL of the graphics device implementations.
Simon also went through and added some other features as well, including support for non-square pixels and some scaling things that I never got working correctly.
So, things have been moving along even though it may appear that I dropped off the face of the planet for a while. I'm going to be updating RExecServer soon, which means a dependence on R-devel, to use the converged device and it's new API (which is also available to any GUI developer).
Simon also went through and added some other features as well, including support for non-square pixels and some scaling things that I never got working correctly.
So, things have been moving along even though it may appear that I dropped off the face of the planet for a while. I'm going to be updating RExecServer soon, which means a dependence on R-devel, to use the converged device and it's new API (which is also available to any GUI developer).
Tuesday, September 4, 2007
Not Dead
Just busy trying to get some software sorted out before feature freeze. Turns out to be a lot of work when you break R. :-)
Subscribe to:
Posts (Atom)