Added the ability to clear an object from the global cache. This is rarely

needed (and can be potentially dangerous if the object depends on certain
startup methods to run and/or holds temporary attributes on themselves -
these will all be lost due to a new instance being created. It is hoever
necessary when it comes to renaming Exits - since the Command on the exit
must then change name too, recaching the Exit will also update the command.
Resolves issue 223.
This commit is contained in:
Griatch 2012-04-15 21:46:43 +02:00
parent 91ec33b9a7
commit 464aa8ca9e
3 changed files with 38 additions and 28 deletions

View file

@ -1015,6 +1015,9 @@ class CmdName(ObjManipCommand):
if aliases: if aliases:
obj.aliases = aliases obj.aliases = aliases
astring = " (%s)" % (", ".join(aliases)) astring = " (%s)" % (", ".join(aliases))
# fix for exits - we need their exit-command to change name too
if obj.destination:
obj.flush_from_cache()
caller.msg("Object's name changed to '%s'%s." % (newname, astring)) caller.msg("Object's name changed to '%s'%s." % (newname, astring))

View file

@ -41,7 +41,7 @@ from src.server.models import ServerConfig
from src.typeclasses import managers from src.typeclasses import managers
from src.locks.lockhandler import LockHandler from src.locks.lockhandler import LockHandler
from src.utils import logger, utils from src.utils import logger, utils
from src.utils.utils import make_iter, is_iter, has_parent, to_unicode, to_str from src.utils.utils import make_iter, is_iter, to_unicode, to_str
__all__ = ("Attribute", "TypeNick", "TypedObject") __all__ = ("Attribute", "TypeNick", "TypedObject")
@ -1447,3 +1447,11 @@ class TypedObject(SharedMemoryModel):
return any(True for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) return any(True for hpos, hperm in enumerate(_PERMISSION_HIERARCHY)
if hperm in [p.lower() for p in self.permissions] and hpos > ppos) if hperm in [p.lower() for p in self.permissions] and hpos > ppos)
return False return False
def flush_from_cache(self):
"""
Flush this object instance from cache, forcing an object reload. Note that this
will kill all temporary attributes on this object since it will be recreated
as a new Typeclass instance.
"""
self.__class__.flush_cached_instance(self)

View file

@ -1,18 +1,17 @@
""" """
This is mostly unmodified from the original idmapper. This is mostly unmodified from the original idmapper.
Evennia changes: Evennia changes:
The cache mechanism was changed from a WeakValueDictionary to a The cache mechanism was changed from a WeakValueDictionary to a
normal dictionary. The old way caused very hard-to-diagnose bugs normal dictionary. The old way caused very hard-to-diagnose bugs
over long periods of time (which Evennia requires) over long periods of time (which Evennia requires)
added save() overloading mechanism to update cache added save() overloading mechanism to update cache
added get_all_cached_instances() for convenient access to objects added get_all_cached_instances() for convenient access to objects
""" """
from twisted.internet import reactor
from django.db.models.base import Model, ModelBase from django.db.models.base import Model, ModelBase
from manager import SharedMemoryManager from manager import SharedMemoryManager
@ -29,8 +28,8 @@ class SharedMemoryModelBase(ModelBase):
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
""" """
this method will either create an instance (by calling the default implementation) this method will either create an instance (by calling the default implementation)
or try to retrieve one from the class-wide cache by infering the pk value from or try to retrieve one from the class-wide cache by infering the pk value from
args and kwargs. If instance caching is enabled for this class, the cache is args and kwargs. If instance caching is enabled for this class, the cache is
populated whenever possible (ie when it is possible to infer the pk value). populated whenever possible (ie when it is possible to infer the pk value).
""" """
def new_instance(): def new_instance():
@ -38,7 +37,7 @@ class SharedMemoryModelBase(ModelBase):
#if _get_full_cache: #if _get_full_cache:
# return cls.__instance_cache__.values() # return cls.__instance_cache__.values()
instance_key = cls._get_cache_key(args, kwargs) instance_key = cls._get_cache_key(args, kwargs)
# depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance # depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance
if instance_key is None: if instance_key is None:
@ -52,11 +51,11 @@ class SharedMemoryModelBase(ModelBase):
return cached_instance return cached_instance
def _prepare(cls): def _prepare(cls):
# this is the core cache # this is the core cache
cls.__instance_cache__ = {} #WeakValueDictionary() cls.__instance_cache__ = {} #WeakValueDictionary()
super(SharedMemoryModelBase, cls)._prepare() super(SharedMemoryModelBase, cls)._prepare()
class SharedMemoryModel(Model): class SharedMemoryModel(Model):
# XXX: this is creating a model and it shouldn't be.. how do we properly # XXX: this is creating a model and it shouldn't be.. how do we properly
@ -64,12 +63,12 @@ class SharedMemoryModel(Model):
__metaclass__ = SharedMemoryModelBase __metaclass__ = SharedMemoryModelBase
class Meta: class Meta:
abstract = True abstract = True
def _get_cache_key(cls, args, kwargs): def _get_cache_key(cls, args, kwargs):
""" """
This method is used by the caching subsystem to infer the PK value from the constructor arguments. This method is used by the caching subsystem to infer the PK value from the constructor arguments.
It is used to decide if an instance has to be built or is already in the cache. It is used to decide if an instance has to be built or is already in the cache.
""" """
result = None result = None
# Quick hack for my composites work for now. # Quick hack for my composites work for now.
@ -78,18 +77,18 @@ class SharedMemoryModel(Model):
else: else:
pk = cls._meta.pk pk = cls._meta.pk
# get the index of the pk in the class fields. this should be calculated *once*, but isn't atm # get the index of the pk in the class fields. this should be calculated *once*, but isn't atm
pk_position = cls._meta.fields.index(pk) pk_position = cls._meta.fields.index(pk)
if len(args) > pk_position: if len(args) > pk_position:
# if it's in the args, we can get it easily by index # if it's in the args, we can get it easily by index
result = args[pk_position] result = args[pk_position]
elif pk.attname in kwargs: elif pk.attname in kwargs:
# retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a # retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a
# a ForeignKey. # a ForeignKey.
result = kwargs[pk.attname] result = kwargs[pk.attname]
elif pk.name != pk.attname and pk.name in kwargs: elif pk.name != pk.attname and pk.name in kwargs:
# ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead # ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead
result = kwargs[pk.name] result = kwargs[pk.name]
if result is not None and isinstance(result, Model): if result is not None and isinstance(result, Model):
# if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key # if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key
result = result._get_pk_val() result = result._get_pk_val()
@ -98,8 +97,8 @@ class SharedMemoryModel(Model):
def get_cached_instance(cls, id): def get_cached_instance(cls, id):
""" """
Method to retrieve a cached instance by pk value. Returns None when not found Method to retrieve a cached instance by pk value. Returns None when not found
(which will always be the case when caching is disabled for this class). Please (which will always be the case when caching is disabled for this class). Please
note that the lookup will be done even when instance caching is disabled. note that the lookup will be done even when instance caching is disabled.
""" """
return cls.__instance_cache__.get(id) return cls.__instance_cache__.get(id)
@ -110,7 +109,7 @@ class SharedMemoryModel(Model):
Method to store an instance in the cache. Method to store an instance in the cache.
""" """
if instance._get_pk_val() is not None: if instance._get_pk_val() is not None:
cls.__instance_cache__[instance._get_pk_val()] = instance cls.__instance_cache__[instance._get_pk_val()] = instance
cache_instance = classmethod(cache_instance) cache_instance = classmethod(cache_instance)
def get_all_cached_instances(cls): def get_all_cached_instances(cls):
@ -120,20 +119,20 @@ class SharedMemoryModel(Model):
def _flush_cached_by_key(cls, key): def _flush_cached_by_key(cls, key):
del cls.__instance_cache__[key] del cls.__instance_cache__[key]
_flush_cached_by_key = classmethod(_flush_cached_by_key) _flush_cached_by_key = classmethod(_flush_cached_by_key)
def flush_cached_instance(cls, instance): def flush_cached_instance(cls, instance):
""" """
Method to flush an instance from the cache. The instance will always be flushed from the cache, Method to flush an instance from the cache. The instance will always be flushed from the cache,
since this is most likely called from delete(), and we want to make sure we don't cache dead objects. since this is most likely called from delete(), and we want to make sure we don't cache dead objects.
""" """
cls._flush_cached_by_key(instance._get_pk_val()) cls._flush_cached_by_key(instance._get_pk_val())
#key = "%s-%s" % (cls, instance.pk) #key = "%s-%s" % (cls, instance.pk)
#print "uncached: %s (%s: %s) (total cached: %s)" % (instance, cls.__name__, len(cls.__instance_cache__), len(TCACHE)) #print "uncached: %s (%s: %s) (total cached: %s)" % (instance, cls.__name__, len(cls.__instance_cache__), len(TCACHE))
flush_cached_instance = classmethod(flush_cached_instance) flush_cached_instance = classmethod(flush_cached_instance)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
#ssave = super(SharedMemoryModel, self).save #ssave = super(SharedMemoryModel, self).save
#reactor.callInThread(ssave, *args, **kwargs) #reactor.callInThread(ssave, *args, **kwargs)