可可中的自定义主应用程序循环

时间:2016-08-04 13:25:32

标签: objective-c cocoa

我一直在关注Handmade Hero项目,其中Casey Muratori从头开始创建一个完整的游戏引擎而不使用库。 这个引擎是高度可移植的,因为它渲染了自己的位图,平台特定代码然后将其绘制到屏幕上。

在Windows下通常有一个主应用程序循环,您可以在其中放置应重复执行的代码,直到应用程序终止。然而在Cocoa中没有这样的东西。一旦调用[NSApp run]; int main()就变得毫无用处,你必须将代码放入委托方法中才能执行它。 但这不是我想要做的事情。我在网上发现了一些代码已经完全符合我的要求,但是代码有一些缺陷,或者说我只是不知道如何处理它。

#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);
}

这是应用程序到目前为止需要运行的所有代码。只需将其复制并粘贴到空的Xcode命令行项目中即可。

但是,当您在应用程序运行时检查硬件时,您将看到CPU几乎以100%运行。我读到这个问题的原因是应用程序必须一直搜索新事件,因为自定义运行循环。

此外,由于循环不能控制委托对象,因此- (BOOL)windowShouldClose:(id)sender之类的方法不再起作用。

问题:

  1. 有没有更好的方法来实现一个自定义的主应用程序循环,其风格如下并不浪费CPU时间和我使用的那样多?

    while(running){ //做东西 }

  2. 如果按下窗口的关闭按钮,如何终止应用程序,因为Application Delegate和Window Delegate方法不再响应?

  3. 我现在花了好几个小时在网上搜索Cocoa中的自定义主要运行循环,但只是遇到了多线程和那些对我没帮助的东西。

    你能推荐一些能帮助我的在线资源/书籍吗?我真的想亲自处理一些处理不寻常的东西的资源,比如自定义的运行循环。

2 个答案:

答案 0 :(得分:0)

简单地说,这不是将Cocoa应用程序放在一起的表现如何;正如你通过代理方法问题发现的那样,而不是Cocoa框架是如何工作的。

除了AppKit中的大量代码需要调用NSApplicationMain()这一事实之外,整个系统也会这样做,并且根据您的方法,您可能最终会在应用程序中执行一些烦人的事情,例如与Dock和Launchpad的互动很差。

还有捆绑资源等问题;除其他外,它会影响代码签名,这意味着除非这只是你个人使用的东西,否则你将难以将应用程序带到世界各地。

您要做的是设置一个窗口,其中单个视图用于绘图,一个线程用作逻辑循环(视情况而定)。做框架,告诉系统更新视图,快乐。

答案 1 :(得分:0)

我知道已经晚了两年,但是我在Cocoa With Love上发现了一篇文章,您可能会觉得有用。

https://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html

我尝试过以这种方式实现主事件循环,查看了CPU的使用情况,这比我早些时候讲得更合理。我不完全知道为什么,但是我将对此做更多研究。