Source code for fsleyes.gl.resources

#!/usr/bin/env python
#
# resources.py - Simple manager for shared OpenGL resources.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module implements a simple API for managing shared OpenGL resources.
Some OpenGL resources (e.g. textures) take up a lot of memory so it makes
sense to share these resources where possible, instead of creating and
maintaining multiple copies. The API defined in this module consists of the
following functions:


.. autosummary::
   :nosignatures:

   exists
   get
   set
   delete


On creation, resources must be given a unique name, referred to as a
``key``. Subsequent accesses to the resource are performed by specifying this
key. As an example, let's say that we have a :class:`.Image` called
``myImage``::


    import fsleyes.gl.resources as glresources
    import fsleyes.gl.textures  as gltextures
    import fsl.data.image       as fslimage

    image = fslimage.Image('data.nii.gz')

We wish to create an :class:`.ImageTexture` which can be shared by multiple
users. All users of this texture can use the :func:`get` function to access
the texture. The first call to :func:`get` will result in the texture being
created, whereas subsequent calls will return a reference to the existing
texture, and will increase its reference count::

    texture = glresources.get(
        'myTexture',
         gltextures.ImageTexture,
         'myTexture',
         image,
         interp=gl.GL_LINEAR)


.. note:: Here, we have used ``'myTexture'`` as the resource key. In practice,
          you will need to use something that is guaranteed to be unique
          throughout your application.


When a user of the texture no longer needs the texture, it must call the
:func:`delete` method. Calls to :func:`delete` will decrement the reference
count; when this count reaches zero, the texture will be destroyed::


    glresources.delete('myTexture')


.. note:: This module was written for managing OpenGL :class:`.Texture`
          objects, but can actually be used with any type - the only
          requirement is that the type defines a method called ``destroy``,
          which performs any required clean-up operations.
"""

import logging


log = logging.getLogger(__name__)


[docs] def exists(key): """Returns ``True`` if a resource with the specified key exists, ``False`` otherwise. """ return key in _resources
[docs] def get(key, createFunc=None, *args, **kwargs): """Return a reference to the resource wiuh the specified key. If no resource with the given key exists, and ``createFunc`` is not ``None``, the resource is created, registered, and returned. If the resource does not exist, and ``createFunc`` is ``None``, a :exc:`KeyError` is raised. :arg key: Unique resource identifier. :arg createFunc: If the resource does not exist, and this argument is provided, it will be called to create the resource. All other positional and keyword arguments will be passed through to the ``createFunc``. """ r = _resources.get(key, None) if r is None and createFunc is None: raise KeyError(f'Resource {key} does not exist') if r is not None: r.refcount += 1 log.debug('Resource %s reference count ' 'increased to %s', str(key), r.refcount) return r.resource if createFunc is not None: return set(key, createFunc(*args, **kwargs))
[docs] def set(key, resource, overwrite=False): """Create a new resource, or update an existing one. :arg key: Unique resource identifier. :arg resource: The resource itself. :arg overwrite: If ``False`` (the default), and a resource with the specified ``key`` already exists, a :exc:`KeyError` is raised. Otherwise, it is assumed that a resource with the specified ``key`` exists - the existing resource is replaced with the specified ``resource``. """ if (not overwrite) and (key in _resources): raise KeyError(f'Resource {key} already exists') if not overwrite: log.debug('Adding resource {}'.format(str(key))) r = _Resource(key, resource) r.refcount += 1 _resources[key] = r log.debug('Resource %s reference count ' 'increased to %s', str(key), r.refcount) else: log.debug('Updating resource %s', str(key)) _resources[key].resource = resource return resource
[docs] def delete(key): """Decrements the reference count of the resource with the specified key. When the resource reference count reaches ``0``, the ``destroy`` method is called on the resource. :arg key: Unique resource identifier. """ r = _resources[key] r.refcount -= 1 log.debug('Resource %s reference count ' 'decreased to %s', str(key), r.refcount) if r.refcount <= 0: log.debug('Destroying resource %s', str(key)) _resources.pop(key) r.resource.destroy()
[docs] class _Resource: """Internal type which is used to encapsulate a resource, and the number of active references to that resources. The following attributes are available on a ``_Resource``: ============ ============================================================ ``key`` The unique resource key. ``resource`` The resource itself. ``refcount`` Number of references to the resource (initialised to ``0``). ============ ============================================================ """
[docs] def __init__(self, key, resource): """Create a ``_Resource``. :arg key: The unique resource key. :arg resource: The resource itself. """ self.key = key self.resource = resource self.refcount = 0
_resources = {} """A dictionary containing ``{key : _Resource}`` mappings for all resources that exist. """