Skip to content

feat(vm): add show_error function#353

Open
emiyl wants to merge 16 commits into
ButterscotchRunner:mainfrom
emiyl:show_error
Open

feat(vm): add show_error function#353
emiyl wants to merge 16 commits into
ButterscotchRunner:mainfrom
emiyl:show_error

Conversation

@emiyl

@emiyl emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Adds in the debug show_error function. In GameMaker, this would open a popup window but due to this project needing to support multiple frontends, I've just made it print the error instead and optionally exit the game if the game marks it necessary.

When this is called: show_error("Assertion failed: " + _msg, true);, the following is output:

Game error: Assertion failed: vertex_format_end returned invalid format
Game aborted due to show_error() call.

and the game quits.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Added desktop popups for Windows and macOS on all backends, and for Linux on SDL2 and SDL3. I haven't tested Windows or Linux but I think it should work.

For GLFW and SDL1, macOS and Windows use the native system calls to show an error message. This functionality is included in SDL2 and SDL3 though, so we can just use that.

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Do we need a dedicated Mac thing? Doesn't glfw have a message box function we can use instead? Also we would need a ton of modification to the build system to add objective C because the makefile is only setup to build .c files. My iOS port handled this by building everything with -ObjC and just putting the objc in a .c file but idk if that would work well with cmake.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Makefile modification is minimal:

if(APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    target_sources(butterscotch PRIVATE src/desktop/backends/platform/macos.m)
    find_library(COCOA_LIBRARY Cocoa REQUIRED)
    target_link_libraries(butterscotch PRIVATE ${COCOA_LIBRARY})
endif()

As far as I can tell, GLFW and SDL1 do not have error popup functions.

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Honestly if glfw doesn't have a message box API then maybe it would be better to just phone it in and call printf and not have a popup then to deal with objc, unless there's some super clean way to do it I haven't thought of. Maybe we could just tell cmake to always pass -ObjC

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Makefile modification is minimal:

if(APPLE)
    target_sources(butterscotch PRIVATE src/desktop/backends/platform/macos.m)
    find_library(COCOA_LIBRARY Cocoa REQUIRED)
    target_link_libraries(butterscotch PRIVATE ${COCOA_LIBRARY})
endif()

As far as I can tell, GLFW and SDL1 do not have error popup functions.

That code block is cmake not make

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Isn't cmake used to build the project? I don't see what's particularly problematic about this implementation. I guess it's possible to do it in pure C using the objc runtime but I don't think it's necessary - it's not hard to compile objc and it only affects macOS.

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Isn't cmake used to build the project? I don't see what's particularly problematic about this implementation. I guess it's possible to do it in pure C using the objc runtime but I don't think it's necessary - it's not hard to compile objc and it only affects macOS.

We also have a makefile build for legacy systems that can't run cmake easily, and stuff with weird incomplete C libraries. I guess we could add some of the incomplete C library stuff to cmake too but I haven't bothered.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

fwiw it does also print to console no matter what, the popup is just a nice bonus

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

We also have a makefile build for legacy systems that can't run cmake easily, and stuff with weird incomplete C libraries. I guess we could add some of the incomplete C library stuff to cmake too but I haven't bothered.

i guess but this wouldn't affect those systems surely

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

The makefile build also supports modern stuff that uses the desktop backend. I use it over cmake because it lets me skip actually running cmake before I can run make.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

fair enough, let me see if i can update the makefile too

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

I eventually wanna make the makefile build support all platforms. I don't want to drop macOS support from the makefile, especially when old macOS versions are a key use case for the makefile.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Would adding this into the Makefile in the GLFW3, GLFW2 and SDL1 if statements not work?

ifeq ($(OS),Darwin)
SRCS += src/desktop/backends/platform/macos.m
LIBS += -framework Cocoa
endif

Makefile doesn't work for me and i cba figure out why but that should be fine surely

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

okay i figured out why it wasn't working and now it's all fixed and it all works yay

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

I don't want to duplicate the compile rule is the issue

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Can you just put it in a .c file and pass -ObjC on Darwin, put it in a header that's guarded by #ifdef __APPLE__

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

how about this? no duplicate compiler rule there

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

the issue with just putting it as a C file is that then

#import <Cocoa/Cocoa.h>

void show_error_box(const char *message)
{
    @autoreleasepool {
        NSAlert *alert = [[NSAlert alloc] init];

        [alert setMessageText:@"Error"];
        [alert setInformativeText:
            [NSString stringWithUTF8String:message]];

        [alert addButtonWithTitle:@"OK"];
        [alert setAlertStyle:NSAlertStyleCritical];

        [alert runModal];
    }
}

becomes

#include <stdio.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <objc/message.h>

static id send_id(id obj, SEL sel)
{
    return ((id (*)(id, SEL))objc_msgSend)(obj, sel);
}

static id send_id_id(id obj, SEL sel, id arg)
{
    return ((id (*)(id, SEL, id))objc_msgSend)(obj, sel, arg);
}

static void send_void_id(id obj, SEL sel, id arg)
{
    ((void (*)(id, SEL, id))objc_msgSend)(obj, sel, arg);
}

static void send_void_int(id obj, SEL sel, int arg)
{
    ((void (*)(id, SEL, int))objc_msgSend)(obj, sel, arg);
}

void show_error_box(const char *message)
{
    Class NSAlert = (Class)objc_getClass("NSAlert");
    Class NSString = (Class)objc_getClass("NSString");

    id alert = send_id(send_id((id)NSAlert, sel_registerName("alloc")),
                       sel_registerName("init"));

    id title = send_id_id((id)NSString,
                          sel_registerName("stringWithUTF8String:"),
                          (id)"Error");

    id text = send_id_id((id)NSString,
                         sel_registerName("stringWithUTF8String:"),
                         (id)message);

    send_void_id(alert, sel_registerName("setMessageText:"), title);
    send_void_id(alert, sel_registerName("setInformativeText:"), text);

    id ok = send_id_id((id)NSString,
                       sel_registerName("stringWithUTF8String:"),
                       (id)"OK");

    send_id_id(alert, sel_registerName("addButtonWithTitle:"), ok);
    send_void_int(alert, sel_registerName("setAlertStyle:"), 2);

    send_id(alert, sel_registerName("runModal"));
}

one of those is a lot more maintainable and readable

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Oh shit does that work? I spent way too fuckin long tryna figure out a way to do that when doing iOS. Good shit.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

🫡

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

the issue with just putting it as a C file is that then

#import <Cocoa/Cocoa.h>

void show_error_box(const char *message)
{
    @autoreleasepool {
        NSAlert *alert = [[NSAlert alloc] init];

        [alert setMessageText:@"Error"];
        [alert setInformativeText:
            [NSString stringWithUTF8String:message]];

        [alert addButtonWithTitle:@"OK"];
        [alert setAlertStyle:NSAlertStyleCritical];

        [alert runModal];
    }
}

becomes

#include <stdio.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <objc/message.h>

static id send_id(id obj, SEL sel)
{
    return ((id (*)(id, SEL))objc_msgSend)(obj, sel);
}

static id send_id_id(id obj, SEL sel, id arg)
{
    return ((id (*)(id, SEL, id))objc_msgSend)(obj, sel, arg);
}

static void send_void_id(id obj, SEL sel, id arg)
{
    ((void (*)(id, SEL, id))objc_msgSend)(obj, sel, arg);
}

static void send_void_int(id obj, SEL sel, int arg)
{
    ((void (*)(id, SEL, int))objc_msgSend)(obj, sel, arg);
}

void show_error_box(const char *message)
{
    Class NSAlert = (Class)objc_getClass("NSAlert");
    Class NSString = (Class)objc_getClass("NSString");

    id alert = send_id(send_id((id)NSAlert, sel_registerName("alloc")),
                       sel_registerName("init"));

    id title = send_id_id((id)NSString,
                          sel_registerName("stringWithUTF8String:"),
                          (id)"Error");

    id text = send_id_id((id)NSString,
                         sel_registerName("stringWithUTF8String:"),
                         (id)message);

    send_void_id(alert, sel_registerName("setMessageText:"), title);
    send_void_id(alert, sel_registerName("setInformativeText:"), text);

    id ok = send_id_id((id)NSString,
                       sel_registerName("stringWithUTF8String:"),
                       (id)"OK");

    send_id_id(alert, sel_registerName("addButtonWithTitle:"), ok);
    send_void_int(alert, sel_registerName("setAlertStyle:"), 2);

    send_id(alert, sel_registerName("runModal"));
}

one of those is a lot more maintainable and readable

Oh no my idea was literally having objc in a .c file, not writing actual C. If you pass -ObjC to the compiler it will still work. Having .m files is better tho so if there's no issues with the current makefile thing then I prefer that

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

oo right sorry i misunderstood lmao

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

You should merge upstream cuz with the msvc 4.0 patch there was a minor change to the makefile and there might be a conflict.

Comment thread src/desktop/backends/platform/macos.m Outdated
@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

now the diff shows the changes from those commits. github being dumb. maybe rebase instead of merging

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author
#import <Cocoa/Cocoa.h>

void show_error_box(const char *message)
{
#if __has_feature(objc_arc)
    @autoreleasepool {
#else
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#endif

        NSAlert *alert = [[NSAlert alloc] init];

        [alert setMessageText:@"Error"];
        [alert setInformativeText:[NSString stringWithUTF8String:message]];
        [alert addButtonWithTitle:@"OK"];

#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
        [alert setAlertStyle:NSAlertStyleCritical];
#else
        [alert setAlertStyle:NSCriticalAlertStyle];
#endif

        [alert runModal];

#if !__has_feature(objc_arc)
        [alert release];
#endif

#if __has_feature(objc_arc)
    }
#else
    [pool drain];
#endif
}

This should work on OSX 10.3 and above. Before that, NSAlert doesn't exist.

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

why have all those
#if __has_feature(objc_arc)
why not just use the non-arc path always?

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

we could but if someone does compile it with arc then it'll fail 🤷 this is the most portable i could get it

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Also I think right now the code should in theory work on 10.0, though maybe not in practice since I think SDL 1.2 won't build. Maybe an older version of SDL 1.2 will build. Worst case just put the whole block behind a compile-time version check so it can build on 10.2 and below, though a runtime check would be nicer.

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

i mean NSAlert doesn't exist on 10.0 so you'd have to use NSRunAlertPanel or something

@emiyl

emiyl commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

guess we could do

#include <AvailabilityMacros.h>

#import <AppKit/AppKit.h>

void show_error_box(const char *message)
{
#if __has_feature(objc_arc)
    @autoreleasepool {
#else
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#endif

#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1030

        NSAlert *alert = [[NSAlert alloc] init];

        [alert setMessageText:@"Error"];
        [alert setInformativeText:[NSString stringWithUTF8String:message]];
        [alert addButtonWithTitle:@"OK"];

#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
        [alert setAlertStyle:NSAlertStyleCritical];
#else
        [alert setAlertStyle:NSCriticalAlertStyle];
#endif

        [alert runModal];

#if !__has_feature(objc_arc)
        [alert release];
#endif

#else

        NSRunAlertPanel(
            @"Error",
            [NSString stringWithUTF8String:message],
            @"OK",
            nil,
            nil
        );

#endif

#if !__has_feature(objc_arc)
    [pool drain];
#endif

#if __has_feature(objc_arc)
    }
#endif
}

@Un1q32

Un1q32 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

I need to get out my powermac again because I can't remember if the old apple compilers on 10.2 even defined the modern version macros at all. I'll prolly do that tomorrow maybe, or later today if i cant sleep.

i mean NSAlert doesn't exist on 10.0 so you'd have to use NSRunAlertPanel or something

My idea was just to do nothing and fallback to printf on stuff that can't do NSAlert, but more portability is always welcome.

@emiyl

emiyl commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

there we go - full macOS 10 compatibility lol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants