Anut-py / h-raylib

Haskell bindings for Raylib
https://hackage.haskell.org/package/h-raylib
Apache License 2.0
80 stars 13 forks source link

Provide low-level API to raylib without automatic freeing (WindowResources) #55

Closed oshyshko closed 2 months ago

oshyshko commented 4 months ago

It's enjoyable to have a raylib API in Haskell that takes care about Foreign stuff (pointers and marshaling), so one doesn't have to.

However, at the moment h-raylib enforces a particular memory management approach that may be not good for certain cases or favorable by some programmers. It would be nice for a library user programmer have a choice whether to use raylib without WindowResources facility.

Here is a great article on different levels of APIs Haskell (please, note that it's HTTP, you might need to instruct the browser to render it): http://blog.haskell-exists.com/yuras/posts/a-begginers-guide-to-over-engineering.html

Note, how the API of the lowest level allows building higher level APIs on top of it, but not vice versa.

Please, note that raylib philosophy is about simplicity and not enforcing things, but rather leaving a door open to have them.

Low-level API with Closeable

Here's an example of a low API that allows automatic resource freeing, but can be opted out if not wanted:

with :: Closeable a => IO a -> (a -> IO b) -> IO b
with = flip bracket close

class                     Closeable a       where close :: a -> IO ()
instance                  Closeable Image   where close = ...
instance                  Closeable Texture where close = ...
instance                  Closeable Font    where close = ...
instance (Closeable c) => Closeable [c]     where close = mapM_ close

User programmers can decide how they want to write:

t <- openTexture "1.png"
...
close t

...or...

with (openText "1.png") $ \t -> do
  ...

...or something else.

With this design, user programmers can opt-out from using bracket/with and use something like resourcet or managed, if they want (but let's not focus on these approaches here).

Another important thing: user programmers are not forced to pass WindowsResources to every API call -- especially if they favor other resource-handling techniques such as with/close.

If a user programmer want, he can still implement WindowResources approach in their code on top of low-level API and track all the resources (e.g. this can be useful for runtime introspection). Also, he may go even further and push the WindowResources to a Reader monad, so he doesn't have to carry it around -- that's up to the user programmer.

Anut-py commented 4 months ago

h-raylib exposes a native version of every function, just prepend it with c' (e.g. c'initWindow). This uses raw Ptrs and no automatic memory management, I think this is what you're looking for. To free the resources from CPU but not GPU, you can use rlFree from the Freeable typeclass. To free the resources from both CPU and GPU, you can use c'unload* (e.g. c'unloadImage), then Foreign.Marshall.Alloc.free.

oshyshko commented 4 months ago

Thank you for your answer.

What I would really like to have (as a user programmer) is these functions without WindowResources arguments and add* tracking functionality in them. https://github.com/Anut-py/h-raylib/blob/master/src/Raylib/Core/Textures.hs#L650-L673

...and have a single close function to free things instead of 2 or 3 calls (which is error-prone and context-switching for user programmer -- I would very much prefer not to keep in my head which ones should freed from CPU, GPU or something else -- just closing/freeing with one call).

Sure, it could be doable just to copy-paste all such functions (there's around 100 of them) into my module and then remove WindowResources and addX from all. Not quite good, but doable.

But it's also not possible, because these fns depend on helpers from module Raylib.Internal.Foreign such as withFreeable, pop, popCArray, popCString -- they are hidden.

Is there a better way?

Anut-py commented 4 months ago

I'll have to think of a good solution for this. I might make a Closeable typeclass as you suggested.

oshyshko commented 4 months ago

Great! It would be really great to stay away from pointers and Foreign.

It seems like an extract refactoring of from the existing functions (the ones tracking via WindowResources) -- so it should possible to re-use new fns back from where they extracted + to have a new API.

Things come to my mind such as Raylib.Closeable module or, of if without a module, fns like openImageCloseable, openFontCloseable next to existing ones.

Anut-py commented 2 months ago

Done in 5.5.0.0, reopen this issue if there are any problems