I'm not saying polling is always better than using interrupts. In this particular case, where we're not using the listen mode and sending radio + MCU to sleep ASAP, pin polling is better than interrupts because we can use the interrupt pins of the AVR for other stuff.
It is just that using an interrupt pin like you're doing it in your library, with the current features implemented, not using listen mode, just doesn't make any big sense, except from saving cycles for regularly polling a pin.
Speaking of saving cycles: polling the IRQ registers would certainly use more time to process than to simply poll the GPIO of the AVR.
Although it would be a feasable solution if you want to check all the interrupts like you mentioned.
My previous question, which you didn't quite answer, was whether the radio can receive messages while it is asleep. That would be the only reason for me to hook it up to an interrupt pin of the AVR (if I want the radio to sleep by default as well) but I doubt that it can do that (receiveing messages while asleep), at least when not using listen mode.
Regarding spaghetti code, I guess that's a question of how it is implemented. I still think, the non-blocking way would be better. You usually don't want your code to stop anywhere and wait for a timeout or something like that. You could fire a callback instead, which would notify you about the timeout. Also, if the library was implemented non-blocking, we could make use of the interrupt and maybe call the radio.process() function (which doesn't currently exist, obviously), more often in the code so radio stuff would be processed more often in between the code, than now. That way, interrupt events would get processed faster than now.
Of course, I understand that this approach probably wouldn't be as easy to understand by all the Arduino beginners out there but on the other hand, I'm seeing a great opportunity to educate people. Good, comprehensive examples and documentation can really push people forwards.
That all being said, I guess a good approach would be to make the interrupt pin in your library optional and to let people choose whether they want to poll or not.
On small chips like AVRs we have a choice, to block while the transceiver changes states, to ensure we're ready to transmit or that a packet was transmitted. Those are really the places where it "blocks". We can implement some sleep and other things to squeeze more sleep out of that but I believe otherwise the interrupts are the better approach to using the packet ready which I see as an event you don't want to miss, like after you just polled.
Wait. You are still talking about not wanting to miss the packet ready event.
First of all, you cannot miss the event, according to my understanding, because the DIO0 pin STAYS HIGH (it is active high) until you clear the FIFO buffer (by e.g. reading it). There is NO way you gonna miss that event.
Second, which stays valid even if I'd be wrong in the first part: if one is calling the function radio.receiveDone(), this function looks whether the _haveData boolean has been set by the ISR. How often do you call radio.receiveDone()? I'm doing it in my main loop in one of my stuff-processing functions. receiveDone() is being called in every loop cycle and between that, all other code is being executed. So the chance of "missing" the event (which isn't even possible according to my first statement) is just as high as if you would directly poll the GPIO whether it is in a HIGH state or not.
The only way of missing the event would be, if the DIO0 pin would go LOW again, before you had a chance to poll it. But that, according to my understanding of the datasheet, is not possible because DIO0 will stay high until you clear the FIFO buffer (as I already mentioned). Depending on the amount of code the AVR has to process, it can take quite a while until the AVR gets to read the FIFO buffer ANYWAY, because your ISR only sets a flag and doesn't trigger FIFO reading in any direct way. So IF there was any expiry of the FIFO buffer or the dataready event, we would hit it anyway, regardless of interrupt or pin polling.
All that only applies if you don't want to wake up your MCU using the radio, using an interrupt pin of the MCU.
edit:
And again, because I think there is still a very basic misunderstanding - you wrote earlier:
In my lib I use an interrupt to flush the transceiver FIFO in internal buffers, and free up the transceiver for the next packet ready, as soon as possible.
YES, you are setting the flag _haveData almost instantaneously but the point in time where the data is being read from the FIFO and therefore also when it is cleared, depends solely on the user code because one needs to call receiveDone(). Depending on the code, this can (should not but can) take ages.
Same applys to polling the DIO pin, except, that the pin gets polled in every loop cycle PLUS when checking (receiveDone()). So the data read operation from the FIFO happens exactly as fast as if you would use the interrupt pin. Absolutely no difference.