【问题标题】:Custom main application loop in cocoa可可中的自定义主应用程序循环
【发布时间】:2016-08-04 13:25:31
【问题描述】:

我一直在关注 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 时间?

    同时(运行){ //做东西 }

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

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

您能否推荐一些对我有帮助的在线资源/书籍?我真的很想获得一些资源来处理不寻常的东西,比如自定义运行循环。

【问题讨论】:

  • 这不是咨询/讨论网站。你的问题太笼统了。见How to Ask。这不是C!不要垃圾标签!

标签: objective-c cocoa


【解决方案1】:

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

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

我尝试以这种方式实现主事件循环,查看 CPU 使用率,它比我之前得到的更合理。我不完全知道为什么,但我会对此进行更多研究。

【讨论】:

    【解决方案2】:

    简单地说,这并不是将行为良好的 Cocoa 应用程序组合在一起的方式;正如您通过委托方法发现的问题,而不是 Cocoa 框架的工作原理。

    除了 AppKit 中的许多代码都希望调用 NSApplicationMain() 这一事实之外,整个系统也是如此,并且使用您的方法,您可能最终会导致您的应用程序做很多烦人的事情,比如交互不佳使用 Dock 和 Launchpad。

    还有bundle资源等问题;其中会影响代码签名,这意味着除非这只是您为个人使用而做的事情,否则您将很难将应用程序推向世界。

    您要做的是设置一个带有单个视图的单个窗口来进行绘图,并根据需要设置一个线程来充当逻辑循环。做框架,告诉系统更新视图,开心就好。

    【讨论】:

    • 好的,谢谢您的回复。但是你能知道除了使用委托之外,是否还有其他退出应用程序的方法?
    • @user148013 不要。以通常的方式设置应用程序(使用NSApplicationMain() 并调用[NSApp terminate:nil]
    • 我不会!我保证!我只是想知道。 “如果要了解更大的奥秘,就必须研究其所有方面,而不仅仅是绝地教条的狭隘观点!”
    • @user148013 退出应用程序的另一种方法是使用exit(),它确实做到了这一点。它应该只在调用 NSApplicationMain 之前使用,或者如果您的应用程序以某种方式无法修复地卡住(但如果您可以检测到这种情况,它不存在,所以基本上永远不会。)这是核选项。
    • 当按下红色 X 时如何识别?当然没有代表?我必须在某处将我的跑步设置为假。当然只是理论上的!
    猜你喜欢
    • 2015-04-06
    • 2022-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多