I have been following the Handmade Hero project where Casey Muratori creates a complete game engine from scratch without the use of libraries. This engine is highly portable since it renders its own bitmap which the platform specific code then draws to the screen.
Under windows there normally is an main application loop where you can put your code which should be executed repeatedly until the application gets terminated. However there is no such thing in Cocoa. As soon as [NSApp run];
is called int main()
gets pretty much useless and you have to put your code into delegate methods to get it executed.
But thats not how I want do do it. I found some code online where someone already did exactly what I wanted but the code has some flaws or lets say I just don't know how to deal with it.
#import <Cocoa/Cocoa.h>
#import <CoreGraphics/CoreGraphics.h>
#include <stdint.h>
#define internal static
#define local_persist static
#define global_variable static
typedef uint8_t uint8;
global_variable bool running = false;
global_variable void *BitmapMemory;
global_variable int BitmapWidth = 1024;
global_variable int BitmapHeight = 768;
global_variable int BytesPerPixel = 4;
global_variable int XOffset = 0;
global_variable int YOffset = 0;
@class View;
@class AppDelegate;
@class WindowDelegate;
global_variable AppDelegate *appDelegate;
global_variable NSWindow *window;
global_variable View *view;
global_variable WindowDelegate *windowDelegate;
@interface AppDelegate: NSObject <NSApplicationDelegate> {
}
@end
@implementation AppDelegate
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
// Cocoa will kill your app on the spot if you don't stop it
// So if you want to do anything beyond your main loop then include this method.
running = false;
return NSTerminateCancel;
}
@end
@interface WindowDelegate : NSObject <NSWindowDelegate> {
}
@end
@implementation WindowDelegate
- (BOOL)windowShouldClose:(id)sender {
running = false;
return YES;
}
-(void)windowWillClose:(NSNotification *)notification {
if (running) {
running = false;
[NSApp terminate:self];
}
}
@end
@interface View : NSView <NSWindowDelegate> {
@public
CGContextRef backBuffer_;
}
- (instancetype)initWithFrame:(NSRect)frameRect;
- (void)drawRect:(NSRect)dirtyRect;
@end
@implementation View
// Initialize
- (id)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
int bitmapByteCount;
int bitmapBytesPerRow;
bitmapBytesPerRow = (BitmapWidth * 4);
bitmapByteCount = (bitmapBytesPerRow * BitmapHeight);
BitmapMemory = mmap(0,
bitmapByteCount,
PROT_WRITE |
PROT_READ,
MAP_ANON |
MAP_PRIVATE,
-1,
0);
//CMProfileRef prof;
//CMGetSystemProfile(&prof);
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
backBuffer_ = CGBitmapContextCreate(BitmapMemory, BitmapWidth, BitmapHeight, 8, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
//CMCloseProfile(prof);
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
CGContextRef gctx = [[NSGraphicsContext currentContext] graphicsPort];
CGRect myBoundingBox;
myBoundingBox = CGRectMake(0, 0, 1024, 768);
//RenderWeirdGradient(XOffset, YOffset);
CGImageRef backImage = CGBitmapContextCreateImage(backBuffer_);
CGContextDrawImage(gctx, myBoundingBox, backImage);
CGImageRelease(backImage);
}
internal void RenderWeirdGradient(int BlueOffset, int GreenOffset) {
int Width = BitmapWidth;
int Height = BitmapHeight;
int Pitch = Width*BytesPerPixel;
uint8 *Row = (uint8 *)BitmapMemory;
for(int Y = 0;
Y < BitmapHeight;
++Y)
{
uint8 *Pixel = (uint8 *)Row;
for(int X = 0;
X < BitmapWidth;
++X)
{
*Pixel = 0;
++Pixel;
*Pixel = (uint8)Y + XOffset;
++Pixel;
*Pixel = (uint8)X + YOffset;
++Pixel;
*Pixel = 255;
++Pixel;
}
Row += Pitch;
}
}
@end
static void createWindow() {
NSUInteger windowStyle = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask;
NSRect screenRect = [[NSScreen mainScreen] frame];
NSRect viewRect = NSMakeRect(0, 0, 1024, 768);
NSRect windowRect = NSMakeRect(NSMidX(screenRect) - NSMidX(viewRect),
NSMidY(screenRect) - NSMidY(viewRect),
viewRect.size.width,
viewRect.size.height);
window = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
id menubar = [[NSMenu new] autorelease];
id appMenuItem = [[NSMenuItem new] autorelease];
[menubar addItem:appMenuItem];
[NSApp setMainMenu:menubar];
// Then we add the quit item to the menu. Fortunately the action is simple since terminate: is
// already implemented in NSApplication and the NSApplication is always in the responder chain.
id appMenu = [[NSMenu new] autorelease];
id appName = [[NSProcessInfo processInfo] processName];
id quitTitle = [@"Quit " stringByAppendingString:appName];
id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
[appMenu addItem:quitMenuItem];
[appMenuItem setSubmenu:appMenu];
NSWindowController * windowController = [[NSWindowController alloc] initWithWindow:window];
[windowController autorelease];
//View
view = [[[View alloc] initWithFrame:viewRect] autorelease];
[window setContentView:view];
//Window Delegate
windowDelegate = [[WindowDelegate alloc] init];
[window setDelegate:windowDelegate];
[window setAcceptsMouseMovedEvents:YES];
[window setDelegate:view];
// Set app title
[window setTitle:appName];
// Add fullscreen button
[window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
[window makeKeyAndOrderFront:nil];
}
void initApp() {
[NSApplication sharedApplication];
appDelegate = [[AppDelegate alloc] init];
[NSApp setDelegate:appDelegate];
running = true;
[NSApp finishLaunching];
}
void frame() {
@autoreleasepool {
NSEvent* ev;
do {
ev = [NSApp nextEventMatchingMask: NSAnyEventMask
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (ev) {
// handle events here
[NSApp sendEvent: ev];
}
} while (ev);
}
}
int main(int argc, const char * argv[]) {
initApp();
createWindow();
while (running) {
frame();
RenderWeirdGradient(XOffset, YOffset);
[view setNeedsDisplay:YES];
XOffset++;
YOffset++;
}
return (0);
}
This is all the code the application needs to run so far. Just copy and paste it into an empty Xcode Command Line Project and it will work.
However as you inspect the hardware while the application is running you will see that the CPU is pretty much running at 100%. I read that the reason for this problem is that the application has to search for new events the whole time because of the custom run loop.
Moreover since the loop doesn't hand control over to the delegate objects, methods like - (BOOL)windowShouldClose:(id)sender
do not work anymore.
Questions:
Is there a better way of implementing a custom main application loop with the style below that doesn't waste CPU time as much as the one I'm using?
while (running) { //do stuff }
How do I get the application terminated with pressing the window's close button since the Application Delegate and Window Delegate methods do not respond anymore?
I've spent hours now searching the web for custom main run loops in Cocoa but just came across multithreading and stuff that wouldn't help me.
Could you recommend some online resources/books that would help me in my case? I would really like to get my hands on some resources that handle unusual stuff like a custom run loop.
#define
s at the top.. – Sandglass