可可中的自定义主应用程序循环 [英] Custom main application loop in cocoa

查看:54
本文介绍了可可中的自定义主应用程序循环的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在关注Handmade Hero项目,在该项目中,Casey Muratori从零开始创建了完整的游戏引擎,而没有使用任何库.该引擎具有高度的可移植性,因为它呈现了自己的位图,然后平台特定的代码将其绘制到屏幕上.

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.

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

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

到目前为止,这是应用程序需要运行的所有代码.只需将其复制并粘贴到一个空的Xcode命令行项目中,它将起作用.

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.

但是,当您在应用程序运行时检查硬件时,您会看到CPU几乎以100%的速度运行.我读到这个问题的原因是由于自定义运行循环,应用程序必须始终搜索新事件.

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.

此外,由于循环不会将控制权移交给委托对象,因此-(BOOL)windowShouldClose:(id)sender 之类的方法不再起作用.

Moreover since the loop doesn't hand control over to the delegate objects, methods like - (BOOL)windowShouldClose:(id)sender do not work anymore.

问题:

  1. 是否有更好的方法来实现自定义主应用程序循环,其样式如下,这种样式不会浪费CPU时间像我正在使用的那样?

  1. 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 }

由于Application Delegate和Window Delegate方法不再响应,如何通过按下窗口的关闭按钮来终止应用程序?

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?

我已经花了几个小时在Web上搜索Cocoa中的自定义主运行循环,但是遇到了多线程和其他对我无济于事的事情.

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.

推荐答案

简而言之,这并不是行为良好的Cocoa应用程序的组合方式.就像您通过委托方法发现的那样,而不是Cocoa框架的工作原理.

This, simply put, isn't how well-behaved Cocoa applications are put together; and as you've discovered through your delegate method problems, not how the Cocoa framework works.

除了AppKit中的许多代码期望调用 NSApplicationMain()的事实外,整个系统也是如此,并且使用您的方法,您最终可能会导致应用程序执行一堆烦人的事情,例如与Dock和Launchpad的交互不良.

Besides the fact that a lot of code in AppKit expects NSApplicationMain() to be called, the system at large does as well, and with your approach you will probably end up with your application doing a bunch of annoying things like interacting poorly with the Dock and Launchpad.

还有捆绑资源等问题;这尤其会影响代码签名,这意味着除非您仅将其用于个人用途,否则您将很难将应用程序推向世界.

There's also the issue of bundle resources and so on; which impacts, among other things, code signing, meaning that unless this is something you do for personal use only, you're going to have trouble getting the application out into the world.

您想要做的是设置一个带有单个视图的窗口来绘制图形,并根据需要设置一个线程作为逻辑循环.做框架,告诉系统更新视图,开心一点.

What you want to do is to set up a single window with a single view to do the drawing, and a thread to act as the logic loop, as appropriate. Do the frame, tell the system to update the view, be happy.

这篇关于可可中的自定义主应用程序循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆