sphere-group / pegasus

The Pegasus API for Sphere 2.0
BSD 2-Clause "Simplified" License
1 stars 0 forks source link

Async API #26

Open fatcerberus opened 8 years ago

fatcerberus commented 8 years ago

Sphere's internal event loop has long been exposed to the API in a rudimentary capacity. Sphere 1.x has SetUpdateScript() and SetRenderScript(), which only work in the map engine. minisphere brought DispatchScript() to the table, renamed to system.dispatch() in minisphere 4.0, which queues a function to be called on the next engine tick (in practice, on the next FlipScreen()). Async has always been kind of an afterthought in Sphere, because Sphere games have historically been written like C--largely sequential, often with independent "event loops" intermingled with game logic.

If we want to move Sphere forward though, we have to start thinking in terms of async and a central event loop. Platforms like the Web are async-only, and a system like Sphere 1.x would never work in a browser, as has been brought up many times before on the forums.

For my part, I'm implementing an API in minisphere which expands on system.dispatch() to allow more control over the event queue. For example, you'd be able to set up recurring jobs (such as update and render code), and later cancel those if needed.

This issue is to discuss alternatives for a standardized API for such a system. Developers would be able to use such a system to write code which would work both in minisphere as well as a potential "Web Sphere" which implemented the same API, with no changes.

fatcerberus commented 8 years ago

Current design used in minisphere is:

job = Dispatch.now(asyncFunc);  // one-shot, from message pump
job = Dispatch.onUpdate(func);  // recurring, once per frame
job = Dispatch.onFlip(func);  // recurring, on flipscreen

And to stop a pending job from running:

job.cancel();
joskuijpers commented 8 years ago

How about:

let job = new Job(func); Dispatch.now(job) job.cancel() ?

also, replace now with once, as it shouldn’t actually execute now but in the next tick.

On 01 Nov 2016, at 21:46, Bruce Pascoe notifications@github.com wrote:

Current design used in minisphere is:

job = Dispatch.now(asyncFunc); // one-shot, from message pump job = Dispatch.onUpdate(func); // recurring, once per frame job = Dispatch.onFlip(func); // recurring, on flipscreen And to stop a pending job from running:

job.cancel(); — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/sphere-group/pegasus/issues/26#issuecomment-257690867, or mute the thread https://github.com/notifications/unsubscribe-auth/AEyN0Lpp5D7KwA60FAIY1j04XcJNhN3Oks5q56U1gaJpZM4KktY1.

fatcerberus commented 8 years ago

I don't know that it makes sense to have a Job object before you've actually dispatched something? Until then you don't have a job, just a function.

Also it's awkward: It suggests you can dispatch the same job more than once, but if you do that, what does it mean to do job.cancel()? Does it cancel all instances of the job, only the most recent one? It's cleaner to create the job object on dispatch. Then there's no confusion as to what .cancel() means.

I can rename Dispatch.now() but it seems clear at least to me: you're dispatching the job now, but it's called in the next tick.

fatcerberus commented 8 years ago

I guess it would make sense to have an undispatched Job object if you want to pass it around for reuse--but in that case you can just pass the function around since JS has first-class functions? I guess it might make sense for extensibility if there were more types of jobs than just "call this function". An interesting proposition, something worth thinking about at least.

fatcerberus commented 8 years ago

One thing I like about this API setup over, say, the Node.js event system is that it's much easier to cancel jobs. In Node, to remove an event listener you have to keep a reference to the callback around so that you can pass it to removeListener() (which can sometimes be awkward since func.bind(...) !== func). With the Dispatch setup as long as you store the token returned by the dispatch call, you can freely pass in closures, lambdas, or bind()'d functions directly. You still have to keep a reference to something, but the code ends up looking cleaner.

fatcerberus commented 8 years ago

I also added Dispatch.later(timeout, fn) for time-delayed calls a la setTimeout().

joskuijpers commented 8 years ago

You are right, having undispatched is odd, especially when multiple times dispatching. Nvm me :P

On 02 Nov 2016, at 06:47, Bruce Pascoe notifications@github.com wrote:

I also added Dispatch.later(timeout, fn) for time-delayed calls a la setTimeout().

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/sphere-group/pegasus/issues/26#issuecomment-257778348, or mute the thread https://github.com/notifications/unsubscribe-auth/AEyN0My9s3v-qkV0t0RyBVwLOqB9__W_ks5q6CQEgaJpZM4KktY1.

fatcerberus commented 8 years ago

A useful feature I'm thinking of would be for Dispatch.onFlip() to take an optional "priority" argument that determines render order (highest priority = called last, so that it renders "on top"). That would provide most of the benefits of the threads module right in the core, for very little extra complexity.