From f0a2c34eca87e24912b2a244bbc1d8362ceb58ec Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 28 Feb 2019 13:59:49 +0530 Subject: [PATCH] Port cocoa backed to use glfw mainloop --- glfw/backend_utils.c | 8 +-- glfw/cocoa_init.m | 115 +++++++++++++++++++++++++++++++++++-- glfw/cocoa_monitor.m | 2 +- glfw/cocoa_platform.h | 11 +++- glfw/cocoa_window.m | 89 +++++++++-------------------- glfw/glfw3.h | 129 ------------------------------------------ glfw/internal.h | 2 +- glfw/window.c | 29 ---------- kitty/glfw-wrapper.c | 9 --- kitty/glfw-wrapper.h | 12 ---- kitty/glfw.c | 11 ++-- 11 files changed, 155 insertions(+), 262 deletions(-) diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index aca3b9783..b006e091d 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -65,11 +65,11 @@ addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, for (nfds_t i = 0; i < eld->which##_count; i++) { \ if (eld->which[i].id == item_id) { \ eld->which##_count--; \ + if (eld->which[i].callback_data && eld->which[i].free) { \ + eld->which[i].free(eld->which[i].id, eld->which[i].callback_data); \ + eld->which[i].callback_data = NULL; eld->which[i].free = NULL; \ + } \ if (i < eld->which##_count) { \ - if (eld->which[i].callback_data && eld->which[i].free) { \ - eld->which[i].free(eld->which[i].id, eld->which[i].callback_data); \ - eld->which[i].callback_data = NULL; eld->which[i].free = NULL; \ - } \ memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \ } \ update_func(eld); break; \ diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index 1e719d2e6..d53dcd6d1 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -324,7 +324,7 @@ is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) { int _glfwPlatformInit(void) { - _glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; _glfw.ns.helper = [[GLFWHelper alloc] init]; [NSThread detachNewThreadSelector:@selector(doNothing:) @@ -392,11 +392,13 @@ int _glfwPlatformInit(void) _glfwInitJoysticksNS(); _glfwPollMonitorsNS(); + [pool drain]; return GLFW_TRUE; } void _glfwPlatformTerminate(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (_glfw.ns.displayLinks.lock) { _glfwClearDisplayLinks(); [_glfw.ns.displayLinks.lock release]; @@ -443,9 +445,7 @@ void _glfwPlatformTerminate(void) _glfwTerminateNSGL(); _glfwTerminateJoysticksNS(); - - [_glfw.ns.autoreleasePool release]; - _glfw.ns.autoreleasePool = nil; + [pool drain]; } const char* _glfwPlatformGetVersionString(void) @@ -456,3 +456,110 @@ const char* _glfwPlatformGetVersionString(void) #endif ; } + +static GLFWtickcallback tick_callback = NULL; +static void* tick_callback_data = NULL; +static bool tick_callback_requested = false; + +void _glfwDispatchTickCallback() { + if (tick_callback) { + tick_callback_requested = false; + tick_callback(tick_callback_data); + } +} + +void _glfwPlatformRequestTickCallback() { + if (!tick_callback_requested) { + tick_callback_requested = true; + _glfwCocoaPostEmptyEvent(TICK_CALLBACK_EVENT_TYPE, 0, false); + } +} + +void _glfwPlatformStopMainLoop(void) { + tick_callback = NULL; + [NSApp stop:nil]; + _glfwPlatformPostEmptyEvent(); +} + +void _glfwPlatformRunMainLoop(GLFWtickcallback callback, void* data) { + tick_callback = callback; + tick_callback_data = data; + [NSApp run]; +} + + +typedef struct { + NSTimer *os_timer; + unsigned long long id; + bool repeats; + double interval; + GLFWuserdatafun callback; + void *callback_data; + GLFWuserdatafun free_callback_data; +} Timer; + +static Timer timers[128] = {{0}}; +static size_t num_timers = 0; + +static inline void +remove_timer_at(size_t idx) { + if (idx < num_timers) { + Timer *t = timers + idx; + if (t->os_timer) { [t->os_timer invalidate]; t->os_timer = NULL; } + if (t->callback_data && t->free_callback_data) { t->free_callback_data(t->id, t->callback_data); t->callback_data = NULL; } + num_timers--; + if (idx < num_timers) { + memmove(timers + idx, timers + idx + 1, sizeof(timers[0]) * (num_timers - idx)); + } + } +} + +static void schedule_timer(Timer *t) { + t->os_timer = [NSTimer scheduledTimerWithTimeInterval:t->interval repeats:(t->repeats ? YES: NO) block:^(NSTimer *os_timer) { + for (size_t i = 0; i < num_timers; i++) { + if (timers[i].os_timer == os_timer) { + timers[i].callback(timers[i].id, timers[i].callback_data); + if (!timers[i].repeats) remove_timer_at(i); + break; + } + } + }]; +} + +unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) { + static unsigned long long timer_counter = 0; + if (num_timers >= sizeof(timers)/sizeof(timers[0]) - 1) { + _glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added"); + return 0; + } + Timer *t = timers + num_timers++; + t->id = ++timer_counter; + t->repeats = repeats; + t->interval = interval; + t->callback = callback; + t->callback_data = callback_data; + t->free_callback_data = free_callback; + schedule_timer(t); + return timer_counter; +} + +void _glfwPlatformRemoveTimer(unsigned long long timer_id) { + for (size_t i = 0; i < num_timers; i++) { + if (timers[i].id == timer_id) { + remove_timer_at(i); + break; + } + } +} + +void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, GLFWbool enabled) { + for (size_t i = 0; i < num_timers; i++) { + if (timers[i].id == timer_id) { + Timer *t = timers + i; + if (t->os_timer) { [t->os_timer invalidate]; t->os_timer = NULL; } + t->interval = interval; + if (enabled) schedule_timer(t); + break; + } + } +} diff --git a/glfw/cocoa_monitor.m b/glfw/cocoa_monitor.m index c0bf5f5fd..d53588fd6 100644 --- a/glfw/cocoa_monitor.m +++ b/glfw/cocoa_monitor.m @@ -252,7 +252,7 @@ static CVReturn displayLinkCallback( } [_glfw.ns.displayLinks.lock unlock]; if (notify) { - _glfwCocoaPostEmptyEvent(RENDER_FRAME_REQUEST_EVENT_TYPE, displayID); + _glfwCocoaPostEmptyEvent(RENDER_FRAME_REQUEST_EVENT_TYPE, displayID, true); } return kCVReturnSuccess; } diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index b34061414..49c2e1712 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -36,7 +36,12 @@ typedef void* id; typedef void* CVDisplayLinkRef; #endif -#define RENDER_FRAME_REQUEST_EVENT_TYPE 1 +typedef enum { + EMPTY_EVENT_TYPE, + RENDER_FRAME_REQUEST_EVENT_TYPE, + TICK_CALLBACK_EVENT_TYPE +} EventTypes; + typedef VkFlags VkMacOSSurfaceCreateFlagsMVK; typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long); @@ -129,7 +134,6 @@ typedef struct _GLFWlibraryNS { CGEventSourceRef eventSource; id delegate; - id autoreleasePool; GLFWbool cursorHidden; TISInputSourceRef inputSource; IOHIDManagerRef hidManager; @@ -199,4 +203,5 @@ void _glfwPollMonitorsNS(void); void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor); void _glfwClearDisplayLinks(); -void _glfwCocoaPostEmptyEvent(short subtype, long data1); +void _glfwCocoaPostEmptyEvent(short subtype, long data1, bool at_start); +void _glfwDispatchTickCallback(); diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 1ce79b565..a8f09f30c 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1401,9 +1401,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) [window->ns.object close]; window->ns.object = nil; - - [_glfw.ns.autoreleasePool drain]; - _glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init]; } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char *title) @@ -1614,10 +1611,6 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _glfwInputWindowMonitor(window, monitor); - // HACK: Allow the state cached in Cocoa to catch up to reality - // TODO: Solve this in a less terrible way - _glfwPlatformPollEvents(); - const NSUInteger styleMask = getStyleMask(window); [window->ns.object setStyleMask:styleMask]; // HACK: Changing the style mask can cause the first responder to be cleared @@ -1754,16 +1747,30 @@ static inline CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) { void dispatchCustomEvent(NSEvent *event) { - if (event.subtype == RENDER_FRAME_REQUEST_EVENT_TYPE) { - CGDirectDisplayID displayID = (CGDirectDisplayID)event.data1; - _GLFWwindow *w = _glfw.windowListHead; - while (w) { - if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) { - w->ns.renderFrameRequested = GLFW_FALSE; - w->ns.renderFrameCallback((GLFWwindow*)w); + switch(event.subtype) { + case RENDER_FRAME_REQUEST_EVENT_TYPE: + { + CGDirectDisplayID displayID = (CGDirectDisplayID)event.data1; + _GLFWwindow *w = _glfw.windowListHead; + while (w) { + if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) { + w->ns.renderFrameRequested = GLFW_FALSE; + w->ns.renderFrameCallback((GLFWwindow*)w); + } + w = w->next; + } } - w = w->next; - } + break; + + case EMPTY_EVENT_TYPE: + break; + + case TICK_CALLBACK_EVENT_TYPE: + _glfwDispatchTickCallback(); + break; + + default: + break; } } @@ -1792,51 +1799,7 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { [_glfw.ns.displayLinks.lock unlock]; } -void _glfwPlatformPollEvents(void) -{ - for (;;) - { - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - - [_glfw.ns.autoreleasePool drain]; - _glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init]; -} - -void _glfwPlatformWaitEvents(void) -{ - // I wanted to pass NO to dequeue:, and rely on PollEvents to - // dequeue and send. For reasons not at all clear to me, passing - // NO to dequeue: causes this method never to return. - NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantFuture] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - [NSApp sendEvent:event]; - - _glfwPlatformPollEvents(); -} - -void _glfwPlatformWaitEventsTimeout(double timeout) -{ - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:date - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event) [NSApp sendEvent:event]; - - _glfwPlatformPollEvents(); -} - -void _glfwCocoaPostEmptyEvent(short subtype, long data1) +void _glfwCocoaPostEmptyEvent(short subtype, long data1, bool at_start) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined @@ -1848,13 +1811,13 @@ void _glfwCocoaPostEmptyEvent(short subtype, long data1) subtype:subtype data1:data1 data2:0]; - [NSApp postEvent:event atStart:YES]; + [NSApp postEvent:event atStart:at_start ? YES : NO]; [pool drain]; } void _glfwPlatformPostEmptyEvent(void) { - _glfwCocoaPostEmptyEvent(0, 0); + _glfwCocoaPostEmptyEvent(0, 0, true); } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 9e4e8f9e3..6f8cbf662 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -3735,135 +3735,6 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window */ GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun cbfun); -/*! @brief Processes all pending events. - * - * This function processes only those events that are already in the event - * queue and then returns immediately. Processing events will cause the window - * and input callbacks associated with those events to be called. - * - * On some platforms, a window move, resize or menu operation will cause event - * processing to block. This is due to how event processing is designed on - * those platforms. You can use the - * [window refresh callback](@ref window_refresh) to redraw the contents of - * your window when necessary during such operations. - * - * Do not assume that callbacks you set will _only_ be called in response to - * event processing functions like this one. While it is necessary to poll for - * events, window systems that require GLFW to register callbacks of its own - * can pass events to GLFW in response to many window system function calls. - * GLFW will pass those events on to the application callbacks before - * returning. - * - * Event processing is not required for joystick input to work. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. - * - * @reentrancy This function must not be called from a callback. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref events - * @sa @ref glfwWaitEvents - * @sa @ref glfwWaitEventsTimeout - * - * @since Added in version 1.0. - * - * @ingroup window - */ -GLFWAPI void glfwPollEvents(void); - -/*! @brief Waits until events are queued and processes them. - * - * This function puts the calling thread to sleep until at least one event is - * available in the event queue. Once one or more events are available, - * it behaves exactly like @ref glfwPollEvents, i.e. the events in the queue - * are processed and the function then returns immediately. Processing events - * will cause the window and input callbacks associated with those events to be - * called. - * - * Since not all events are associated with callbacks, this function may return - * without a callback having been called even if you are monitoring all - * callbacks. - * - * On some platforms, a window move, resize or menu operation will cause event - * processing to block. This is due to how event processing is designed on - * those platforms. You can use the - * [window refresh callback](@ref window_refresh) to redraw the contents of - * your window when necessary during such operations. - * - * Do not assume that callbacks you set will _only_ be called in response to - * event processing functions like this one. While it is necessary to poll for - * events, window systems that require GLFW to register callbacks of its own - * can pass events to GLFW in response to many window system function calls. - * GLFW will pass those events on to the application callbacks before - * returning. - * - * Event processing is not required for joystick input to work. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. - * - * @reentrancy This function must not be called from a callback. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref events - * @sa @ref glfwPollEvents - * @sa @ref glfwWaitEventsTimeout - * - * @since Added in version 2.5. - * - * @ingroup window - */ -GLFWAPI void glfwWaitEvents(void); - -/*! @brief Waits with timeout until events are queued and processes them. - * - * This function puts the calling thread to sleep until at least one event is - * available in the event queue, or until the specified timeout is reached. If - * one or more events are available, it behaves exactly like @ref - * glfwPollEvents, i.e. the events in the queue are processed and the function - * then returns immediately. Processing events will cause the window and input - * callbacks associated with those events to be called. - * - * The timeout value must be a positive finite number. - * - * Since not all events are associated with callbacks, this function may return - * without a callback having been called even if you are monitoring all - * callbacks. - * - * On some platforms, a window move, resize or menu operation will cause event - * processing to block. This is due to how event processing is designed on - * those platforms. You can use the - * [window refresh callback](@ref window_refresh) to redraw the contents of - * your window when necessary during such operations. - * - * Do not assume that callbacks you set will _only_ be called in response to - * event processing functions like this one. While it is necessary to poll for - * events, window systems that require GLFW to register callbacks of its own - * can pass events to GLFW in response to many window system function calls. - * GLFW will pass those events on to the application callbacks before - * returning. - * - * Event processing is not required for joystick input to work. - * - * @param[in] timeout The maximum amount of time, in seconds, to wait. - * - * @reentrancy This function must not be called from a callback. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref events - * @sa @ref glfwPollEvents - * @sa @ref glfwWaitEvents - * - * @since Added in version 3.2. - * - * @ingroup window - */ -GLFWAPI void glfwWaitEventsTimeout(double timeout); - /*! @brief Posts an empty event to the event queue. * * This function posts an empty event from the current thread to the event diff --git a/glfw/internal.h b/glfw/internal.h index ca7ff108b..e40b8fd87 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -786,7 +786,7 @@ _GLFWwindow* _glfwWindowForId(GLFWid id); void _glfwPlatformRunMainLoop(GLFWtickcallback, void*); void _glfwPlatformRequestTickCallback(); void _glfwPlatformStopMainLoop(void); -unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafreefun callback, void *callback_data, GLFWuserdatafreefun free_callback); +unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback); void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, GLFWbool enabled); void _glfwPlatformRemoveTimer(unsigned long long timer_id); diff --git a/glfw/window.c b/glfw/window.c index 0658a2e1a..b341279dd 100644 --- a/glfw/window.c +++ b/glfw/window.c @@ -1133,35 +1133,6 @@ GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* return cbfun; } -GLFWAPI void glfwPollEvents(void) -{ - _GLFW_REQUIRE_INIT(); - _glfwPlatformPollEvents(); -} - -GLFWAPI void glfwWaitEvents(void) -{ - _GLFW_REQUIRE_INIT(); - - _glfwPlatformWaitEvents(); -} - -GLFWAPI void glfwWaitEventsTimeout(double timeout) -{ - _GLFW_REQUIRE_INIT(); - assert(timeout == timeout); - assert(timeout >= 0.0); - assert(timeout <= DBL_MAX); - - if (timeout != timeout || timeout < 0.0 || timeout > DBL_MAX) - { - _glfwInputError(GLFW_INVALID_VALUE, "Invalid time %f", timeout); - return; - } - - _glfwPlatformWaitEventsTimeout(timeout); -} - GLFWAPI void glfwPostEmptyEvent(void) { _GLFW_REQUIRE_INIT(); diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index b6c17e7d1..c6d027d67 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -230,15 +230,6 @@ load_glfw(const char* path) { *(void **) (&glfwSetWindowContentScaleCallback_impl) = dlsym(handle, "glfwSetWindowContentScaleCallback"); if (glfwSetWindowContentScaleCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowContentScaleCallback with error: %s", dlerror()); - *(void **) (&glfwPollEvents_impl) = dlsym(handle, "glfwPollEvents"); - if (glfwPollEvents_impl == NULL) fail("Failed to load glfw function glfwPollEvents with error: %s", dlerror()); - - *(void **) (&glfwWaitEvents_impl) = dlsym(handle, "glfwWaitEvents"); - if (glfwWaitEvents_impl == NULL) fail("Failed to load glfw function glfwWaitEvents with error: %s", dlerror()); - - *(void **) (&glfwWaitEventsTimeout_impl) = dlsym(handle, "glfwWaitEventsTimeout"); - if (glfwWaitEventsTimeout_impl == NULL) fail("Failed to load glfw function glfwWaitEventsTimeout with error: %s", dlerror()); - *(void **) (&glfwPostEmptyEvent_impl) = dlsym(handle, "glfwPostEmptyEvent"); if (glfwPostEmptyEvent_impl == NULL) fail("Failed to load glfw function glfwPostEmptyEvent with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 34977f0c3..903c83a2d 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1711,18 +1711,6 @@ typedef GLFWwindowcontentscalefun (*glfwSetWindowContentScaleCallback_func)(GLFW glfwSetWindowContentScaleCallback_func glfwSetWindowContentScaleCallback_impl; #define glfwSetWindowContentScaleCallback glfwSetWindowContentScaleCallback_impl -typedef void (*glfwPollEvents_func)(); -glfwPollEvents_func glfwPollEvents_impl; -#define glfwPollEvents glfwPollEvents_impl - -typedef void (*glfwWaitEvents_func)(); -glfwWaitEvents_func glfwWaitEvents_impl; -#define glfwWaitEvents glfwWaitEvents_impl - -typedef void (*glfwWaitEventsTimeout_func)(double); -glfwWaitEventsTimeout_func glfwWaitEventsTimeout_impl; -#define glfwWaitEventsTimeout glfwWaitEventsTimeout_impl - typedef void (*glfwPostEmptyEvent_func)(); glfwPostEmptyEvent_func glfwPostEmptyEvent_impl; #define glfwPostEmptyEvent glfwPostEmptyEvent_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 34fb0aefb..36bdc3c1b 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -771,12 +771,6 @@ glfw_terminate(PYNOARG) { Py_RETURN_NONE; } -static PyObject* -glfw_poll_events(PYNOARG) { - glfwPollEvents(); - Py_RETURN_NONE; -} - static PyObject* get_physical_dpi(GLFWmonitor *m) { int width = 0, height = 0; @@ -910,7 +904,11 @@ swap_window_buffers(OSWindow *os_window) { void wakeup_main_loop() { request_tick_callback(); +#ifndef __APPLE__ + // On Cocoa request_tick_callback() uses an event which wakes up the + // main loop anyway glfwPostEmptyEvent(); +#endif } void @@ -1175,7 +1173,6 @@ static PyMethodDef module_methods[] = { METHODB(x11_display, METH_NOARGS), METHODB(x11_window_id, METH_O), METHODB(set_primary_selection, METH_VARARGS), - METHODB(glfw_poll_events, METH_NOARGS), #ifndef __APPLE__ METHODB(dbus_send_notification, METH_VARARGS), #endif