Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resource managing bindings #49

Closed
expipiplus1 opened this issue Apr 1, 2020 · 19 comments
Closed

Add resource managing bindings #49

expipiplus1 opened this issue Apr 1, 2020 · 19 comments

Comments

@expipiplus1
Copy link
Owner

It would be nice to have bracket functions which used ResourceT, Managed or Polysemy.Resource to manage resources. These really shouldn't be too hard to generate, but probably shouldn't go in the main library. I guess while we're here we could also do bindings lifted into MonadIO

The procedure would be, for each module with bracket commands in to write a new module reexporting all but the bracketed commands, as well as the differently typed bracketing command.

It would also be necessary to do the parent modules too, so one could just import Graphics.Vulkan.Core10.ResourceT to get everything but the brackets from Graphics.Vulkan.Core10 as well as the new bracket functions.

Might also be nice to have a Vulkan monad which carries around the instance and device, as well as a separate monad for cmds.

@dpwiz
Copy link
Collaborator

dpwiz commented Apr 30, 2020

Having another monad can be problematic w.r.t. composition with users' "application" stack.

Perhaps it would be better to have not one resource library, but many - one for each composition style.

@expipiplus1
Copy link
Owner Author

That's exactly the plan :)

I've written a small bit of TH towards this goal: https://hackage.haskell.org/package/autoapply

To generate these libraries would just involve mirroring the module tree and adding on .ResourceT or .Polysemy to the end.

It would be nice to have multiple public libraries in this package to accomplish this, but support on Hackage isn't forthcoming...

Hopefully autoapply is easy to use so that these extra wrapper libraries aren't necessary, I'll have a test this weekend to try that.

I recently (not released on hackage yet) changed the types of the "bracketing" functions so that they don't rely on bracket directly: 6ad4598#diff-7b4f6f44bc221c0556d3d6faed15fc51 This allows them to be more easily used with any resource allocation scheme.

@dpwiz
Copy link
Collaborator

dpwiz commented May 3, 2020

I kinda like how bracket-arguments are working but find it too easy to leak resources.

Sometimes I need resources to stay across the entire program run, but sometimes those are scratch buffers and other transient things. One misplaced allocate instead of bracket and a thing sticks forever. In this light autoApply looks like another good length of rope to trip upon. 🤔

@expipiplus1
Copy link
Owner Author

I think this is more a problem with Haskell's overall clumsy/verbose handling of scarce resources rather than with autoapply. After all, it's just writing boilerplate for you.

I do see the problem though, so here are a few potential solutions:

  • Use resourcet's allocate everywhere and make use of the ReleaseKey that resourcet gives you to free things early
  • Manually add a type signature for everything you generate with autoapply
  • Use something like MonadNestedResource
    • This definitely adds rope to shoot yourself with

As an aside, one thing I've encountered is freeing resources (after an exception is thrown) while command buffers are still in flight, the validation layers gets angry about destroying things which are still in use. I don't think it would be appropriate to wait for the device to become idle in such a case...

@expipiplus1
Copy link
Owner Author

I wonder if it would be worth moving the continuation to the last parameter, so one could do

foo = withInstance zero Nothing $ \c d -> do
  -- Create instance
  i <- c
  -- use instance
  ...
  -- Destroy instance
  d i

@dpwiz
Copy link
Collaborator

dpwiz commented May 3, 2020

I've thought about this quite a few times too.

@expipiplus1
Copy link
Owner Author

Cool, I'll make the change then :)

@dpwiz
Copy link
Collaborator

dpwiz commented May 3, 2020

I bump frequently into XxxCreateInfo arguments not being in the final position.

resource <- createStuff bracket (zero { stuff = [] }) Nothing

I don't know if that would be a good change for managed bindings, but I think I'd like having curryable abstractable boilerplate arguments in front and locals like infos and callbacks in the back.

For example:

createResource boi ler plate createInfo callback = ...

myResource = createResource boi ler plate

theMyResource = myResource zero { base = mine }

things =
  theMyResource $ \one ->
    theMyResource $ \two ->
      ...

This should make autoApply calls more intuitive too.

@expipiplus1
Copy link
Owner Author

Yeah, that was exactly the reason it was the first argument initially. It's no big deal to move it back to the front. For continuations though they do feel more at home as the last argument IMO.

-- Not sure why you'd want to do this, but it can be done (or something like it)
foo = do
  (c, d) <- ContT . (.curry) $ withInstance zero Nothing
  ...

The change for the examples in the repo weren't too painful FWIW 1754222

@expipiplus1
Copy link
Owner Author

The more I think about it, the less appropriate vanilla bracket or resourcet seem for this. At the very least they need to be aware that the resources they're in charge off may still be in use elsewhere (on the device). Before freeing a resource they must wait until the fences of any command buffers using this resource have been signaled.

Perhaps something like this would be good:

foo = do
  -- Allocate a resource
  (myReleaseKey, myResource) <- withResource ...

  -- Record a command buffer using myResource
  commandBuffer <- recordMyCommandBuffer myResource

  (myFenceReleaseKey, myFence) <- withFence ...

  mask $ \_ -> do
    queueSubmit commandBuffer myFence
    traverse_ (addFenceToResource myFence) [myReleaseKey, ...]

addFenceToResource fence releaseKey = mask $ \_ -> do
  Just free <- unprotect releaseKey
  register $ do
    waitForFence fence
    free

It would be cool to do automatic tracking of resources being used in commands, but I do think that is out of scope for these bindings.

Doing something like the above does seem like a whole can of worms though, might just be more simple to deviceWaitIdle before terminating

@expipiplus1
Copy link
Owner Author

Now that Hackage supports cabal v3 (haskell/hackage-server#852) resource management bindings for specific libraries could be put into publicly visible sublibraries

@expipiplus1
Copy link
Owner Author

@dpwiz I don't much mind where the continuation parameter goes (first or last), just to summarize:

  • It would be nice in the first position because it's 'curryable', i.e. users may well want to partially apply these functions to the continuation parameter

  • It would be nice in the last position to better match other CPS code, and permit withXxx foo bar $\ c d -> ...

@dpwiz
Copy link
Collaborator

dpwiz commented May 4, 2020

I have a feeling that CPS-style would compose better. And that's more important for a lower-level bindings. Frameworks can put on their gloss later in whatever style they prefer.

@expipiplus1
Copy link
Owner Author

expipiplus1 commented May 5, 2020 via email

@dpwiz
Copy link
Collaborator

dpwiz commented May 11, 2020

BTW, I started a ground-up rewrite of my tutorial scene into RIO-based playground.

The main theme was to split allocated stuff into three groups:

  • permanent - allocated in Managed outer ResourceT at start.
  • context-sensitive - allocated and freed with Acquire inside an "outer" game loop, while keeping game state between the reloads.
  • transient - haven't used yet, maybe I shouldn't.

The stages demarcated by runRIO with explicit derivation of inner environment.

I hesitated using withXxx and brackets arguments, but after a while a found that Acquire r is a monad and mkAcquire is a good match for that bracket. After that it went smoothly again.

It still doesn't draw a thing 😅 so I don't know how slow it is.

@expipiplus1
Copy link
Owner Author

@dpwiz very cool, Please add a link to the readme if you think it'd be a good example for people looking to use this library.

I've started on a nicer example on the resize branch. I'm hoping to model it after some of the techniques described here: https://www.gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine

@dpwiz
Copy link
Collaborator

dpwiz commented May 13, 2020

I've seen "resize" code and it is. so. much. nicer. to have all the stuff in separate modules. This, or maybe I just got more experienced in it with time (=

@dpwiz
Copy link
Collaborator

dpwiz commented Jul 21, 2020

Having played with Data.Acquire.mkAcquire I am now quite satisfied with how it all works. Perhaps this issue is resolved.

@expipiplus1
Copy link
Owner Author

Yeah, I'm very happy with how autoapply and resourcet have been working. For anyone reading this what I do is the following (from here):

autoapplyDecs
  (<> "'") -- Suffix a ' to make new names
  [ 'getDevice -- = MonadVulkan m => m Device
  , 'getInstance
  , 'getAllocator
  , 'noAllocationCallbacks -- = Nothing :: Maybe AllocationCallbacks
  ]
  -- 'Control.Monad.ResourceT.allocate' doesn't subsume the continuation type on the "with" commands, so
  -- put it in the unifying group.
  ['allocate]
  [ 'deviceWaitIdle
  , 'getDeviceQueue
  , 'withInstance
  , 'cmdDraw
  , ...
  ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants