z0w0 / helm

A functionally reactive game engine, with headgear to protect you from the headache of game development provided.
http://helm-engine.org/
MIT License
600 stars 69 forks source link

Refactoring idea #102

Closed koflerdavid closed 8 years ago

koflerdavid commented 8 years ago

Hello,

Recently I got inspired by Elm and the concept of composable reactive GUIs. I didn't just want to do it in the browser though, but also on the desktop. I ran across Helm, which is for games and graphics programming, but what I had in mind was programming GUIs, possibly by using Gtk. So I had the idea of separating the SDL code from the core in order for Helm to also work with Gtk. The Readme suggests Helm was never supposed to be a general GUI toolkit, but I gave it a try nevertheless :)

This pull request contains a refactored version of the Helm libary. The changes are heavy, but they consist of

The interface to the backend is specified as a type class BEngine. To choose a backend only the appropriate packages have to be imported. I took care to not require heavy changes to the examples from the Readme.

I don't think it's necessary or beneficial to accept this pull request, especially since I didn't implement the Gtk backend yet. For me this is more about finding out what the architecture of Helm is capable of, and sharing the results with you. Comments and suggestions are very welcome :)

kasbah commented 8 years ago

Very cool, I had a similar idea when I was last working with Helm.

z0w0 commented 8 years ago

Hey,

(Sorry for wall of text)

So I've actually been working on a complete rework of Helm recently. It's been a while since I've worked on Helm and Haskell in general so development has been extremely slow.

Essentially I've taken a lot of ideas from the rework of Elm recently (which dropped first-class FRP in favour of a very simple version of FRP) but Haskell-ized them. So I guess it's not different to the original version of Helm in that regard, but my aim is to take on more of the Haskell ecosystem this time around.

I love the idea of abstracting SDL away from Helm itself, although I believe a lot of discussion needs to be had around it before we proceed. The reason I say this is my first aim is to first abstract out renderers. Within the new version of Helm, the graphics DSL will remain but will be renamed into Graphics2d. Then at some point I'll be aiming to write a Graphics3D DSL. Then there'll be some concept of an abstract rendererer, i.e. a HelmCairo which will take the Graphics2D DSL and render it within the context of the engine. Then there might be a HelmGL that renders a Graphics3D DSL into the engine.

The reason we can't easily abstract the engine (i.e. SDL2) out with this sort of thing is because each engine will have a way of integrating with OpenGL and rendering surfaces. The renderers that end up existing will need to be agnostic of the type of engine, which is an extremely hard task.. not to say it can't be done through some sort of typeclass magic (and it most likely can), and I'm open to suggestions in that regard.

For those curious, here's the ins and outs of my rewrite:

type Render a {-- haven't figured the exact typing/functionality out of this yet --}

type Cmd a = StateT Engine IO [a]

type Sub a = SignalGen Engine (Signal [a])

data Game a b = Game {
  initial          :: (a, Cmd b),
  update           :: a -> b -> (a, Cmd b),
  subscriptions    :: Sub b,
  view             :: a -> Render ()
}

N.B. These type defs aren't final. Just there current incarnate.

The Render type is something rendered in the context of the Helm engine. I.e. a monad for rendering something to the Helm window's screen. You might do something like the following:

view model = HelmCairo.render $ collage 800 600 []

The Cmd and Sub monads come straight from Elm. I'm not sold on the naming of these yet (I can't decide between the shortened version or long version, but I think I prefer the shortened due to the amount they'll be used).

Have a read of the latest architecture document for a rundown: http://guide.elm-lang.org/architecture/index.html

Essentially, Sub is now a simplified version of a stateful Elerea signal, the difference being that it produces a list of events at every sample. These events are then handed off to update, which takes the model state (which initially is pulled from the first element in the initial tuple), and then an "action type" (which will usually be an enum of possible actions that can be performed in the game). So essentially, rather than writing a foldp yourself, the functionality is now the way all Helm games will be written by default (and conceptually, the best way to write a functionally pure game).

@kasbah you'll notice that the Unchanged/Changed stuff is no longer necessary as Unchanged is now the same as an empty list. I'll be writing some sort of code that diffs the rendered DSL to see whether a re-render is necessary too.

Commands and subscriptions always produce these action enums. And every action enum is run by the engine. A Cmd is just a wrapped IO monad that has the context of the engine. Also, with the types in Elm having access to the async powers of JavaScript by default, in order for me to prevent huge loops of IO actions blocking up the game tick, this means that all IO monads within the Cmd monad are run on light-threads and then the threads send back an array of queued action types to the engine via STM.

Running them on a separate thread has it's drawbacks (particularly for GUI based calls) but it's the only way to get this working nicely and I'm under the impression if it does get working like this, it'll be extremely powerful. I'm under the impression the current state of Helm is simply not powerful enough to develop games in, and with some sort of setup like this, things like network state and physic engine state can be easily integrated into a game. Which is pretty much the defining quality you need in order for a game engine to be usable.

This will be the jump to Helm 1.0.

z0w0 commented 8 years ago

Closing in favour of 1.0.0. Thanks for this though, pretty much triggered the rework of the engine (that and the newer design of Elm). If you notice any mistakes/things your could improve in my Engine typeclass, let me know.