-
Notifications
You must be signed in to change notification settings - Fork 12
Best practices for using cloud-haskell in an online multiplayer game? #19
Comments
While prototyping, I encoutered the following issue: To capture player input (key presses), I poll events using glfwPollEvents which needs to be called from the main thread, once per game loop. But when calling
So my question then is: is there a way to run code inside the |
Hello,
One way around that is to call runProcess inside the loop, instead of
running the loop inside a call to runProcess. Another option is to call
runProcess in an auxiliary thread and communicate with it with channels,
stm or mvars.
A trickier project is to write in distributed-process a variant or
runProcess that runs on the calling thread. This needs to deal correctly
with asynchronous exceptions that distributed-process could send to the
thread after runProcess completes. Perhaps is even trickier if the thread
is allowed to call runProcess a second time.
Good luck,
Facundo
…On Sun, Feb 4, 2018 at 4:52 PM, OlivierSohn ***@***.***> wrote:
While prototyping, I encoutered the following issue:
To capture player input (key presses), I poll events using glfwPollEvents
<http://www.glfw.org/docs/latest/group__window.html#ga37bd57223967b4211d60ca1a0bf3c832>
which needs to be called *from the main thread*, once per game loop.
But when calling glfwPollEvents from code passed to runProcess, I have
this crash, indicating I'm not on the main thread anymore:
2018-02-04 19:57:51.104 imj-game-hamazed-exe[64868:8363909] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'nextEventMatchingMask should only be called from the Main Thread!'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff4e696fcb __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00007fff75338c76 objc_exception_throw + 48
2 AppKit 0x00007fff4c389faf -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 4167
3 imj-game-hamazed-exe 0x000000010f979933 _glfwPlatformPollEvents + 147
4 imj-game-hamazed-exe 0x000000010f96d168 GLFWzmbzm1zi4zi8zi1zmLtjCFy18WuD8hyYAe76SCE_GraphicsziUIziGLFW_pollEvents1_info + 112
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Abort trap: 6
So my question then is: is there a way to run code inside the Process
monad, while being also on the main thread?
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#19 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABJSG8czQ5eFCMzEgCDTBmU3sa-dLluQks5tRgqEgaJpZM4R4kuP>
.
|
@facundominguez I like the idea of running in an auxiliary thread, I think it will incur less overhead and be more flexible than calling runProcess inside the loop. In the meantime I tried a websocket-based approach, where I had to use auxiliary threads, and channels to communicate with them, so it will probably be easily refactorable, once it works, to test with cloud-haskell and compare approaches. I'm not familiar with asynchronous exceptions but from what you explain, the project of making it run on the calling thread sounds tricky indeed! Almost as if it would go against a fundamental underlying design concept? I wonder if there is a fundamental reason why runProcess doesn't run on the calling thread (is it easier to implement supervision this way?), and if such design decisions are publicly documented somewhere? Thanks for your support! |
Almost as if it would go against a fundamental underlying design concept?
I'm not aware of it being a consequence of a widespread design choice, but
maybe Tim Watson might like to weigh in here. I don't think there is a
discussion of this written anywhere, unfortunately.
Best,
Facundo
…On Mon, Feb 5, 2018 at 10:12 AM, OlivierSohn ***@***.***> wrote:
@facundominguez <https://github.com/facundominguez> I like the idea of
running in an auxiliary thread, I think it will incur less overhead and be
more flexible than calling runProcess inside the loop. In the meantime I
tried a websocket <http://hackage.haskell.org/package/websockets>-based
approach, where I had to use auxiliary threads, and channels to communicate
with them, so it will probably be easily refactorable, once it works, to
test with cloud-haskell and compare approaches.
I'm not familiar with asynchronous exceptions but from what you explain,
the project of making it run on the calling thread sounds tricky indeed!
Almost as if it would go against a fundamental underlying design concept? I
wonder if there is a fundamental reason why runProcess doesn't run on the
calling thread (is it easier to implement supervision this way?), and if
such design decisions are publicly documented somewhere?
Thanks for your support!
Olivier
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#19 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABJSG_FoOY_7oY3FpDrIWGz_OR25KylRks5tRv5UgaJpZM4R4kuP>
.
|
There's a /huge/ thread about this... Let me see if I can dig it up over the weekend and post back. |
@hyperthunk Thanks, I'd be interested in reading this thread, if you find it! |
@OlivierSohn sorry this conversation went dead! I had some personal issues that kept me away from the project, but it's under active development again now. If you're still using (or interested in using) cloud haskell, then let me know and I'll migrate this issue to an active repository and we can discuss further. For now I am going to close this issue, though it is not resolved. For the benefit of future readers, the reason Furthermore, I suspect the issue @OlivierSohn runs into is related to the fact that Synchronising cloud haskell code with websocket/webserver code in the way @OlivierSohn described above can be achieved using the distributed-process-client-server library, and is discussed at length in this ticket. For a detailed break down of how we're going to be addressing usability and stability issues surrounding using Cloud Haskell in the next major release, see the discussion around separating actors and typed channels here, and the bigger picture discussion here. To summarise the important bits from the ticket I mentioned above, here is some code snipped from that discussion: data StmServer = StmServer { serverPid :: ProcessId
, writerChan :: TQueue String
, readerChan :: TQueue String
} We start out by defining a server handle, which is good practise for -cilent-server apps as per the docs/tutorials. We will use this to interact with the server process. Since we want to resolve it to a process (for monitoring) and in our test case, to kill it (once we're done), we add the relevant instances from distributed-process-extras to make that easy: instance Resolvable StmServer where
resolve = return . Just . serverPid
instance Killable StmServer where
killProc StmServer{..} = kill serverPid
exitProc StmServer{..} = exit serverPid The client part of the interaction uses a new function exposed through the The callSTM :: forall s a b . (Addressable s)
=> s
-> (a -> STM ())
-> STM b
-> a
-> Process (Either ExitReason b)
callSTM server writeAction readAction input = do
liftIO $ atomically $ writeAction input
awaitResponse server [ matchSTM readAction (return . Right) ] Back to our code then, we implement the client side of our API using this function, and use the handle to (a) ensure we have the relevant echoStm :: StmServer -> String -> Process (Either ExitReason String)
echoStm StmServer{..} = callSTM serverPid
(writeTQueue writerChan)
(readTQueue readerChan) Now for our server implementation. We create the Given our input and output channels, we wire them into the server using the new Here's our server code now: launchEchoServer :: Process StmServer
launchEchoServer = do
(inQ, replyQ) <- liftIO $ do
cIn <- newTQueueIO
cOut <- newTQueueIO
return (cIn, cOut)
let procDef = statelessProcess {
apiHandlers = [
handleCallExternal
(readTQueue inQ)
(writeTQueue replyQ)
(\st (msg :: String) -> reply msg st)
]
}
pid <- spawnLocal $ serve () (statelessInit Infinity) procDef
return $ StmServer pid inQ replyQ Those Finally, the test case, which simply launches the server, calls it synchronously, and puts the reply/response into our result: testExternalCall :: TestResult Bool -> Process ()
testExternalCall result = do
let txt = "hello stm-call foo"
srv <- launchEchoServer
echoStm srv txt >>= stash result . (== Right txt)
killProc srv "done" So, there you have it.... I think |
Hello,
I'm looking for advice on how to turn my single-player local game into an online multiplayer cooperative game, using cloud-haskell for networking:
In this game, each player controls a ship, and can fire a laser from that ship.
So
Client
nodes will send player events to theserver
node, and theserver
will periodically send game updates to theclients
.Are there some real-world examples, or tutorials that I could look at for inspiration?
My thinking sofar is to do something like in ChatClient.hs ChatServer.hs to "connect" the client to the server, and then use typed channels to transfer player events and game updates between them. In case I am missing an important aspect, please tell me!
Thank you :)
The text was updated successfully, but these errors were encountered: