Alphaios Blog

This is the official blog of Alphaios.net. When I feel like rambling I turn here.

7/30/08

Resource Management System Changed

The resource management system I posted before has been drastically changed. If you were using it and are expecting an updated version, expect the new version to be completely incompatible with your code!

Labels: , ,

7/21/08

Resource Management System for Game Engine

I have just completed the resource management system for my upcoming game engine, "Scrollback". It hasn't been tested yet, but the syntax is, at least, correct.

If anyone would like to comment on the API or anything else, please do. I'm looking for some (inteligent) input.

Sorry about the sidebar on the right covering up the code...not much I can do about that right now, unfortunately. :( You should still be able to highlight and copy.

# 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.
""")


Man, it would be nice if Blogger supported a code block feature...

Labels: , , , , , ,

7/17/08

There Are No Dumb Questions

In my recent efforts at creating a data resource management system for my game engine I have entered #python several times to ask questions and get other's opinions. It has helped me so far, so I plan to continue.

The thing that most intrigues me about this experience is that even though I don't always get the answers that I was looking for I always seem to get what I really needed. Now, you may be thinking, "Well, duh, TMG! Those guys know Python--not to mention coding in general-- at least 1,000 times better than you do! " and that is almost always true. However, there is another thing that I have noticed, which has nothing to do with the experience of the experts: Simply talking about my problems helps me to solve them on my own, even when no one has an answer for me. I suppose its because putting enough mental effort to explain my code and ideas well enough that other people can understand them makes me see things from other angles, which in turn helps me to get a better grasp of the problem and therefore makes it much easier to see the solution.

I've known it for a while, but it seems that now that I've finally begun to utilize the social networking power of IRC the need for talking about my coding woes has become much more obvious. The key word in that statement is "begun." I've ONLY begun to really use IRC for practical purposes. I'm still fairly new to the world of IRC--and given my nonstandard perspective on practically everything at all, it will be a while until I really understand IRC. ^_^

That concludes my little rant. I hope you learned something useful that you can apply to your life, etc. etc. blah blah....

Labels: , ,

5/30/08

New AIOS Engine Will Use PHP, Not Python

It looks like there is just no good way to make a dynamic content management system using Python through CGI. In order to make it work you have to do all kinds of crazy stuff, and I don't want to do try it anymore...So, I've quit on the Python AIOS project altogether. Instead, I'm going to go back to the old days: PHP. Yes, good ol' highly-compatible PHP.

Don't get me wrong. I love Python. But it just is no good for making a CMS unless you have the access privileges to your web server to install all the stuff you need, and you usually don't. (I certainly don't.) I will still use it for other things, but for now...Alphaios.net is going back to PHP.

Labels: , , , , ,

5/10/08

MikMod in Python

Well, after neglecting my studies for about a day and I half I finally managed to make libmikmod.so work with Python using ctypes. That means it is, in fact, possible to play tracked module music using Python. Soon I will release a Python package that will wrap MikMod completely and implement some Python classes that will make it very easy to use MikMod in a so-called "Pythonic" way. I will use it to implement the script on Alphaios.net that will automatically display information about modules that I upload.

...at least, that's the plan. Everyone knows how bad I am about keeping those kind of promises. ^_^

Labels: , , , , , , ,