-- | Facilities for working with weak references, finalisers, and factory -- pools. module Graphics.QML.Objects.Weak ( -- * Weak Object References WeakObjRef, toWeakObjRef, fromWeakObjRef, -- * Object Finalisers ObjFinaliser, newObjFinaliser, addObjFinaliser, -- * Factory Pools FactoryPool, newFactoryPool, getPoolObject ) where import Graphics.QML.Internal.BindObj import Graphics.QML.Internal.Objects import Control.Concurrent.MVar import qualified Data.Map as Map import Data.Map (Map) -- | Represents a weak reference to a QML object which wraps the type @tt@. -- -- Unlike ordinary strong references, a weak reference does not prevent the -- QML garbage collector from collecting the underlying object. Weak references -- can be used to monitor the life cycles of QML objects. newtype WeakObjRef tt = WeakObjRef HsQMLObjectHandle -- | Converts a strong 'ObjRef' into a 'WeakObjRef'. toWeakObjRef :: ObjRef tt -> IO (WeakObjRef tt) toWeakObjRef (ObjRef hndl) = do hndl' <- copyObjectHandle hndl True return $ WeakObjRef hndl' -- | Converts a 'WeakObjRef' into a strong 'ObjRef'. -- -- If the underlying QML object has already been collected then the resulting -- 'ObjRef' can be used to reincarnate it. fromWeakObjRef :: WeakObjRef tt -> IO (ObjRef tt) fromWeakObjRef (WeakObjRef hndl) = do hndl' <- copyObjectHandle hndl False return $ ObjRef hndl' -- | Represents an object finaliser function for QML objects which wrap the -- type @tt@. data ObjFinaliser tt = ObjFinaliser HsQMLObjFinaliserHandle -- | Create a new object finaliser from a finaliser function. -- -- Note that at the time the finaliser is called the runtime will have already -- comitted to collecting the underlying QML object. The 'ObjRef' passed into -- the finaliser can be used to reincarnate the object, but this QML object -- will have a distinct identity to the original. newObjFinaliser :: (ObjRef tt -> IO ()) -> IO (ObjFinaliser tt) newObjFinaliser f = do fPtr <- marshalObjFinaliser $ \hPtr -> do hndl <- newObjectHandle hPtr f $ ObjRef hndl final <- hsqmlCreateObjFinaliser fPtr return $ ObjFinaliser final -- | Adds an object finaliser to an QML object. -- -- The finaliser will be called no more than once for each time it was added to -- an object. The timing of finaliser execution is subject to the combined -- behaviour of the Haskell and QML garbage collectors. All outstanding -- finalisers will be run when the QML engine is terminated provided that the -- program does not prematurely exit. addObjFinaliser :: ObjFinaliser tt -> ObjRef tt -> IO () addObjFinaliser (ObjFinaliser final) (ObjRef hndl) = hsqmlObjectAddFinaliser hndl final -- | Represents an object factory which maintains a one-to-one mapping between -- values of type @tt@ and QML object instances. -- -- 'ObjRef's manufactured by the pool are cached using the wrapped type @tt@ as -- the lookup key in an ordered map. The pool uses weak references to -- automatically purge objects which no longer have any strong references -- leading to them from either Haskell or QML code. -- Hence, the pool guarantees that if QML code is using a pool object (e.g. as -- a source for data binding) then the same object instance can be obtained -- again from the pool. Conversely, if an object instance is no longer being -- used then pool will not prevent it from being garbage collected. data FactoryPool tt = FactoryPool { factory_ :: tt -> IO (ObjRef tt), pool_ :: MVar (Map tt (WeakObjRef tt)), finaliser_ :: ObjFinaliser tt } -- | Creates a new 'FactoryPool' using the supplied factory function. newFactoryPool :: (Ord tt) => (tt -> IO (ObjRef tt)) -> IO (FactoryPool tt) newFactoryPool factory = do pool <- newMVar Map.empty finaliser <- newObjFinaliser $ \obj -> do value <- fromObjRefIO obj modifyMVar_ pool (return . Map.delete value) return $ FactoryPool factory pool finaliser -- | Return the pool's canonical QML object for a value of @tt@, either by -- creating it or looking it up in the pool's cache of objects. getPoolObject :: (Ord tt) => FactoryPool tt -> tt -> IO (ObjRef tt) getPoolObject (FactoryPool factory pool finaliser) value = modifyMVar pool $ \pmap -> case Map.lookup value pmap of Just wkObj -> do obj <- fromWeakObjRef wkObj return (pmap, obj) Nothing -> do obj <- factory value addObjFinaliser finaliser obj wkObj <- toWeakObjRef obj return (Map.insert value wkObj pmap, obj)