我一直在关注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
之类的方法不再起作用。
问题:
有没有更好的方法来实现一个自定义的主应用程序循环,其风格如下并不浪费CPU时间和我使用的那样多?
while(running){ //做东西 }
如果按下窗口的关闭按钮,如何终止应用程序,因为Application Delegate和Window Delegate方法不再响应?
我现在花了好几个小时在网上搜索Cocoa中的自定义主要运行循环,但只是遇到了多线程和那些对我没帮助的东西。
你能推荐一些能帮助我的在线资源/书籍吗?我真的想亲自处理一些处理不寻常的东西的资源,比如自定义的运行循环。
答案 0 :(得分:0)
简单地说,这不是将Cocoa应用程序放在一起的表现如何;正如你通过代理方法问题发现的那样,而不是Cocoa框架是如何工作的。
除了AppKit中的大量代码需要调用NSApplicationMain()
这一事实之外,整个系统也会这样做,并且根据您的方法,您可能最终会在应用程序中执行一些烦人的事情,例如与Dock和Launchpad的互动很差。
还有捆绑资源等问题;除其他外,它会影响代码签名,这意味着除非这只是你个人使用的东西,否则你将难以将应用程序带到世界各地。
您要做的是设置一个窗口,其中单个视图用于绘图,一个线程用作逻辑循环(视情况而定)。做框架,告诉系统更新视图,快乐。
答案 1 :(得分:0)
我知道已经晚了两年,但是我在Cocoa With Love上发现了一篇文章,您可能会觉得有用。
https://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
我尝试过以这种方式实现主事件循环,查看了CPU的使用情况,这比我早些时候讲得更合理。我不完全知道为什么,但是我将对此做更多研究。