Resource Management System Changed
Labels: programming, python, resources
This is the official blog of Alphaios.net. When I feel like rambling I turn here.
Labels: programming, python, resources
# sbak/resource.py
"""
This module defines the resource management system.
This version of the resoruce management system has been significantly
toned down from what I originally had it mind. This version does not have
any system of library hierarchies, names, or aliases, as did the original
plan. Instead, there is only the "registry," and all resources are
automatically registered with it when they are created, loaded, or stubbed.
(Incidentally, creating a resource is how a resource is stubbed!)
Complete but untested as of 2008-07-21. - The Music Guy
"""
import os.path
from error import ResourceError
import core._debug
__all__ = (
'init',
'register',
'unregister',
'get',
'get_exists',
'BaseResource'
)
# This maps the IDs of registered resources the actual resources.
_resource = {}
# Current "working directory" of the resource manager
_cwd = ''
def init():
""" Initializes the resource management system.
Must be called before using the resource management system
NOTE: This method doesn't actually do anything right now, but I'm fairly
sure I will need it later, so I'm defining it anyway.
"""
pass
def register(res):
""" Registers a resource with the manager.
You don't need to call this usually because resources will register
themselves automatically, provided they call the BaseResource
initializer on themselves.
Raises error.ResourceError if you attempt to register a resource with an
ID that is in use by another registered resource, or if you attempt to
register an object which does not appear to be a resource.
"""
global _resource
try:
theID = res.id
except AttributeError:
raise ResourceError,"Object being registered does not have a resource ID."
if theID in _resource:
raise ResourceError,'Duplicate registration of resource ID "%s".'%theID
_resource[theID] = res
def unregister(res):
""" Unregisters a resource from the resource manager.
Call this on a resource object or ID that has been registered to make
the system drop its reference to the resource. However, you will no
longer be able to get a reference to the resource by ID.
Raises error.ResourceError if you attempt to unregister a resource that
is not currently registered.
"""
pass # TODO: unregister
def get(id):
""" Returns the resource with the given ID.
Loads the resource explicitly if it has not already been loaded.
Returns None if there isn't a resource registered with the ID.
"""
global _resource
try:
res = _resource[id]
except KeyError:
return None
res.load()
return res
def _get_implicit(id):
""" Internal function.
Implicitly loads the resource with the given ID and returns it.
Returns None if there isn't a resource registered with the ID.
"""
global _resource
try:
res = _resource[id]
except KeyError:
return None
res.load(True)
return res
def get_exists(id):
' Returns whether or not there is a resource registered with the given ID. '
return id in _resource
def set_directory(*d):
""" Sets the "current working directory" of the resource manager.
You may pass a directory in "pieces" as a arbitrary number of arguments.
The pieces will be reassembled using the os.path.join() function.
This directory will be prefixed to the paths of filenames of resources
loaded or stubbed from the time of the calling till the end of the next
cycle.
"""
global _cwd
_cwd = join_paths(*d)
class BaseResource(object):
""" Base class for all resource types.
The following methods should be overwritten by subclasses:
__init__, load, unload
See the documentation for each of these methods for more info.
Additionally, subclasses should define a `save` method for allowing a
resource to save its data to a file. It should be defined as such:
class SomeResourceType(BaseResource):
...
def save(self,filename=None):
...
The `filename` argument is the file to which the resoruce data will be
saved relative to the "current working directory" of the resource
system, which can be determined by calling `resource.get_cwd()`.
If the filename is not present, the value of `self.filename` should be
assumed.
"""
__slots__ = (
'_id', # Unique keyword ID of the resource
'_filename', # Name of file from which this resource was last saved/loaded
'_loaded', # Loaded status (0=not loaded, 1=explicit, 2=implicit)
'_loads', # Number of times this resource has been loaded
'_depend', # List of the IDs of all dependencies
)
def __init__(self, ID=None, filepath=None, ext=None):
""" BaseResource initializer.
You may optionally omit either the ID or the filename, but not both.
The value present will be used in place of the value missing.
In the case of a missing ID but a present filename, the ID will be
the filename without any directory prefixes and without the
extension.
If the `ext` paramater is used, it will be added to the end of the
filename as its extension, but only if the filename was determined
from the ID.
Tip: When loading or stubbing many resources at a time from multiple
directories, it may be helpful to use the resource.chdir() function,
which adds a prefix to all filenames without changing the working
directory.
This should be called by subclasses.
"""
global _cwd
trueFName = filename
trueID = ID
# Subbing ID for filename
if ID is None:
# Fail if neither filename nor ID are present
if filename is None:
raise ValueError,'Must pass at least an ID or a filename.'
trueID = os.path.basename(filename)
trueID = trueID[0:trueID.find('.')]
# Subbing filename for ID
if filename is None:
if ext is None:
trueFName = ID
else:
trueFName = ID + '.' + ext
# Fill in the blanks
self._id = trueID
self._filename = os.path.join(_cwd,trueFName) # Add the cwd prefix
self._loaded = 0 # Not loaded
self._loads = 0 # Has been loaded 0 times
self._depend = [] # Default empty dependency table
# Attempt to register
register(self)
def add_depend(self, *deps):
""" Adds one or more dependencies to the resource's dependency list.
The dependencies should be resources or resource IDs. A duplicate ID
will raise error.ResourceError, as will attempting to use an object
which is not a dependency or string.
"""
for dep in deps:
if dep in self._depend:
raise ResourceError,'Duplicate dependency "%s" for resource "%s".'%(dep,self._id)
if isinstance(dep,str):
self._depend.append(dep)
else:
try:
self._depend.append(dep.id)
except AttributeError:
raise ResourceError,'Dependency %s is not a string and has no id property.'%dep
def remove_depend(self, dep):
""" Removes the given dependency from this resource.
Raises error.ResourceError if the resource does not depend on the
other resource.
"""
if dep not in self_depend:
raise ResourceError,'No dependency "%s" exists for resource "%s".'%(dep,self_id)
self._depend.remove(dep)
def depends_on(self, dep):
""" Returns whether or not this resource depends on the given resource. """
return dep in self._depends
def get_depends(self):
""" Returns a list of all resources this resource depends on. """
return self._depends[:]
def test_depends(self):
""" Make sure all dependencies exist.
Return True if all dependencies exist, otherwise False.
If recurse is true, also tests that dependencies of dependencies
are met, and so on.
TODO: Add recursive dependency checking capabilities
"""
for dep in self._depends:
if not get_exists(dep):
return False
return True
def load(self,implicit=False):
""" Loads the resource data explicitly (default) or implicitly.
Subclasses should overwrite this method to allow their data to be
loaded, but should also call this base method AFTER they do so.
Subclasses should not load data if it is already loaded.
If a resource depends on other resources it should load them
implicitly when this method is called.
Other resources depending on this resource should call this method
with implicit == True when they load it
"""
if implicit:
if self._loaded == 0:
self._loaded = 2
else:
if self._loaded != 1:
self._loaded = 1
def unload(self):
""" Unloads the resource data if it is loaded.
Also decrements the load count.
Subclasses should overwrite this method to allow their data to be
unloaded, but should also call this base method AFTER they do so.
In debug mode, a warning will be issued when this method is called
and there still seems to be other objects using it, or if the data
is not loaded.
"""
if self._loads > 0 and core._debug:
print "Warning: Resource '"+self._id+"' unloaded while still in use!"
self._loads -= 1
if self._loads < 0 or self._loaded==0:
if core._debug:
print "Debug Warning: Attempt to unload resource '"+self._id+"', but it is not loaded !"
self._loads = 0
self._loaded = 0
def unload_implicit(self):
""" Decrements the load count and calls self.unload() if it hits 0.
Returns whether or not the data was actually unloaded.
Does not unload data unless it was loaded implicitly!
In debug mode, a warning will be issued if this method is called
when the data does not seem to be loaded.
"""
self._loads -= 1
if self._loads==0 and self._loaded==2:
self._loads += 1 # This is to compensate for the decrement of the
# unload() method
self.unload()
# In subclasses, the `save` method would usually bed defined about here
def get_id(self):
return self._id
id = property(get_id, None, None,
""" (Read only.) The unique identifying string of the resource. """)
def get_filename(self):
return self._filename
def set_filename(self,v):
if not isinstance(v, str):
raise TypeError,'filename must be a string'
self._filename = v
filename = property(get_filename, set_filename, None,
""" The full path and name of the file to which this resource's data is
saved and from which it is loaded relative to the current working
directory of the program. Should be a string.
""")
def get_loaded(self):
return self._loaded
loaded = property(get_loaded, None, None,
""" (Read only.) The loaded status of this resoruce.
0 = Not loaded,
1 = Loaded explicitly,
2 = Loaded implicitly. The data of implicitly-loaded resources are
unloaded automatically when its load count hits 0.
""")
def get_loads(self):
return self._loads
loads = property(self._loads,None,None,
""" (Read-only.) Number of times the resource has been told to load,
minus the number of times it has been told to unload.
""")
Labels: API, code, dream game, engine, programming, python, resources