NSInternalInconsistencyException running gloss (playIO) on Mac

13 views
Skip to first unread message

Luke Evans

unread,
Aug 2, 2022, 8:59:18 PM8/2/22
to Haskell Gloss
I've just configured a new project with gloss and reflex to play around with some FRP.
I have a couple of reflex bindings for gloss, clearly written a while ago.  The simple one (too simple as it doesn't allow much reflex functionality) invokes playIO on the main thread and works fine.   The binding I want to use does a forkIO and runs playIO in another thread.  Unfortunately, this results in the following exception:
GLUT Fatal Error: internal error: NSInternalInconsistencyException, reason: NSWindow drag regions should only be invalidated on the Main Thread!

Presumably, something changed in MacOS after this code was written.

Has anyone else run into this and, moreover, any known work-arounds?  
I'm girding my loins to have to go into the reflex binding code and somehow move the playIO function into the main thread, but that might be rather tricky.

 

Ben Lippmeier

unread,
Aug 3, 2022, 8:53:02 AM8/3/22
to haskel...@googlegroups.com, Luke Evans
Sorry I haven’t seen that one. What exact version of OSX are you using? I am still on 11.3.1 (Big Sur) I haven’t seen the problem.

Ben.

Luke Evans

unread,
Aug 3, 2022, 1:03:00 PM8/3/22
to Haskell Gloss
I'm on 12.4 (Monterey)

The code I've been using for the gloss/reflex binding, was originally based on the reflex-basic-host package, but I modified that to use reflex's own runHeadlessApp which, as the author of the reflex-basic-host package  points out, is basically equivalent.  

I've copied the relatively short network running function below.  
As can be seen, it runs the gloss playIO function in a separate thread.  
If I compile without -threaded, then this works fine.  However, it is recommended to compile -threaded to get decent frame rates. 
If I do so, and especially if I compile with "-threaded -rtsopts -with-rtsopts=-N -Wall" then I will always get this NSInternalConsistency error. 

Presumably, this works without -threaded because Haskell's lightweight thread' will always run on the main OS thread.  Whereas all other scenarios will have some chance of being scheduled on a thread other than the main thread, and/or suffering from not being run consistently on the same OS thread (I've read that OpenGL uses thread local state, ergo bound threads are necessary).

I have tried:
- Forking with forkOn 0 (in an attempt to get the main thread - but I don't think just saying 'capability 0' implies the main thread)
- Forking with forkOS (but that will definitely schedule on a new OS thread and not the main thread, even if it is bound to that specific OS thread)
- Using GLFW instead of GLUT.  This seemed to make things worse.  No more NSInternalConsistency error for sure, but now the it locks up after a frame or two.  I've read that this could be the Mac switching between GPUs and not handling the context gracefully, but who knows why GLFW would be more sensitive to that than GLUT (which at least works well in the single-threaded case, whichever GPU it is apparently working on). 

I don't know of a way to fork a new Haskell 'fibre' and force it to be scheduled on the main thread when compiled with -threaded.  Even if you could do this, I suppose there may be contention between blocking/foreign IO in runHeadlessApp and in playIO anyway.  

Here's the code I'm using to run the reflex network for gloss currently:

playReflex display color frequency network =
  runHeadlessApp $ do
    picTVar <- liftIO $ newTVarIO blank
    quitTVar <- liftIO $ newTVarIO False

    (tickEvent, tickTrigger) <- newTriggerEvent
    (inputEvent, inputTrigger) <- newTriggerEvent
    (hostQuitEvent, hostQuitTrigger) <- newTriggerEvent

    (dPicture, eQuit) <- network tickEvent (fan $ glossEventMap <$> inputEvent)

    performEvent_ $
      liftIO . atomically . writeTVar picTVar <$>
      updated dPicture

    performEvent_ $
      (liftIO . atomically $ writeTVar quitTVar True) <$ eQuit

    void . liftIO .
      flip forkFinally (\_ -> hostQuitTrigger ()) $
        playIO
          display
          color
          frequency
          ()
          (\_ -> readTVarIO picTVar)
          (\ge _ -> inputTrigger ge)
          (\fl _ -> do
            shouldQuit <- readTVarIO quitTVar
            if shouldQuit
            then exitSuccess
            else tickTrigger fl)

    pure hostQuitEvent

Luke Evans

unread,
Aug 3, 2022, 3:20:02 PM8/3/22
to Haskell Gloss
OK, I took some time out to restructure this code so the playIO is performed on the main thread and the various triggers get received via MVars from a thread running runHeadlessApp in a separate thread (i.e. I swapped what was running in the main thread vs the 'child' thread.  This seems to have fixed my problem, though evidently it would never allow me to run multiple networks concurrently in separate windows, but that's OK for my needs. 
Reply all
Reply to author
Forward
0 new messages