diff --git a/evennia/objects/models.py b/evennia/objects/models.py index 94ee1fa2a..e34ee2da1 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -51,10 +51,10 @@ class ContentsHandler(object): Re-initialize the content cache """ - print "__init__", self.obj, calledby() + #print "__init__", self.obj, calledby() self._pkcache.update(dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk)) - print "init contentscache: self._pkcache:", self.obj, self._pkcache + #print "init contentscache: self._pkcache:", self.obj, self._pkcache, id(self._pkcache), id(self) def get(self, exclude=None): """ @@ -70,15 +70,15 @@ class ContentsHandler(object): if exclude: pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]] else: - print calledby() - print "get: self._pkcache", self.obj, self._pkcache + #print calledby() + #print "get: self._pkcache", self.obj, self._pkcache, id(self._pkcache), id(self) pks = self._pkcache try: return [self._idcache[pk] for pk in pks] except KeyError, err: # this can happen if the idmapper cache was cleared for an object # in the contents cache. If so we need to re-initialize and try again. - print "content_cache.get keyerror:", err, self._pkcache, self._idcache + #print "content_cache.get keyerror:", err, self._pkcache, self._idcache self.init() try: return [self._idcache[pk] for pk in pks] @@ -96,7 +96,7 @@ class ContentsHandler(object): """ self._pkcache[obj.pk] = None - print "add self._pkcache:", self.obj, obj, obj.pk, self._pkcache + #print "add self._pkcache:", self.obj, obj, obj.pk, self._pkcache def remove(self, obj): """ @@ -107,7 +107,7 @@ class ContentsHandler(object): """ self._pkcache.pop(obj.pk, None) - print "remove self._pkcache", self.obj, obj, obj.pk, self._pkcache + #print "remove self._pkcache", self.obj, obj, obj.pk, self._pkcache def clear(self): """ @@ -116,7 +116,7 @@ class ContentsHandler(object): """ self._pkcache = {} self.init() - print "clear _pkcache", self.obj, self._pkcache + #print "clear _pkcache", self.obj, self._pkcache #------------------------------------------------------------ # @@ -272,12 +272,12 @@ class ObjectDB(TypedObject): # remove the safe flag del self._safe_contents_update - print "location_set:", self.key, old_location, self.db_location + #print "location_set:", self.key, old_location, self.db_location # update the contents cache if old_location: old_location.contents_cache.remove(self) if self.db_location: - print "cache add:", self.db_location, self + #print "cache add:", self.db_location, self self.db_location.contents_cache.add(self) except RuntimeError: diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 612851eb7..1977140d3 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -228,7 +228,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ con = self.contents_cache.get(exclude=exclude) - print "contents_get:", self, con, calledby() + #print "contents_get:", self, con, id(self), calledby() return con contents = property(contents_get) @@ -267,7 +267,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return "{}(#{})".format(self.name, self.id) return self.name - def search(self, searchdata, global_search=False, use_nicks=True, # should this default to off? @@ -1134,7 +1133,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - # hooks called when moving the object def at_before_move(self, destination): diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 72967cd12..0ed045b41 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -325,6 +325,14 @@ class AttributeHandler(object): self._catcache.pop(catkey, None) self._cache_complete = False + def reset_cache(self): + """ + Reset cache from the outside. + """ + self._cache_complete = False + self._cache = {} + self._catcache = {} + def has(self, key=None, category=None): """ Checks if the given Attribute (or list of Attributes) exists on @@ -910,7 +918,6 @@ class NAttributeHandler(object): """ self._store[key] = value - self.obj.set_recache_protection() def remove(self, key): """ @@ -922,7 +929,6 @@ class NAttributeHandler(object): """ if key in self._store: del self._store[key] - self.obj.set_recache_protection(self._store) def clear(self): """ diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index ed52d9570..2acadccca 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -376,6 +376,27 @@ class TypedObject(SharedMemoryModel): raise Exception("dbref cannot be deleted!") dbref = property(__dbref_get, __dbref_set, __dbref_del) + def at_idmapper_flush(self): + """ + This is called when the idmapper cache is flushed and + allows customized actions when this happens. + + Returns: + do_flush (bool): If True, flush this object as normal. If + False, don't flush and expect this object to handle + the flushing on its own. + + """ + if self.nattributes.all(): + # we can't flush this object if we have non-persistent + # attributes stored - those would get lost! Nevertheless + # we try to flush as many references as we can. + self.attributes.reset_cache() + self.tags.reset_cache() + return False + # a normal flush + return True + # # Object manipulation methods # diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 84edcf729..e6f9b8e5b 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -212,6 +212,13 @@ class TagHandler(object): self._catcache.pop(catkey, None) self._cache_complete = False + def reset_cache(self): + """ + Reset the cache from the outside. + """ + self._cache_complete = False + self._cache = {} + self._catcache = {} def add(self, tag=None, category=None, data=None): """ diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index 86b95011d..cb00fb6fc 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -79,7 +79,6 @@ class SharedMemoryModelBase(ModelBase): # the dbmodel is either the proxy base or ourselves dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls cls.__dbclass__ = dbmodel - dbmodel._idmapper_recache_protection = False if not hasattr(dbmodel, "__instance_cache__"): # we store __instance_cache__ only on the dbmodel base dbmodel.__instance_cache__ = {} @@ -291,8 +290,10 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): """ try: - if force or not cls._idmapper_recache_protection: + if force or cls.at_idmapper_flush(): del cls.__dbclass__.__instance_cache__[key] + else: + cls._dbclass__.__instance_cache__[key].refresh_from_db() except KeyError: pass @@ -319,27 +320,37 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): cls.__dbclass__.__instance_cache__ = {} else: cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in cls.__dbclass__.__instance_cache__.items() - if obj._idmapper_recache_protection) + if not obj.at_idmapper_flush()) + for ins in cls.__dbclass__.__instance_cache__.itervalues(): + ins.refresh_from_db() #flush_instance_cache = classmethod(flush_instance_cache) # per-instance methods + def at_idmapper_flush(self): + """ + This is called when the idmapper cache is flushed and + allows customized actions when this happens. + + Returns: + do_flush (bool): If True, flush this object as normal. If + False, don't flush and expect this object to handle + the flushing on its own. + """ + return True + def flush_from_cache(self, force=False): """ Flush this instance from the instance cache. Use - `force` to override recache_protection for the object. + `force` to override the result of at_idmapper_flush() for the object. """ pk = self._get_pk_val() - if pk and (force or not self._idmapper_recache_protection): - self.__class__.__dbclass__.__instance_cache__.pop(pk, None) - - def set_recache_protection(self, mode=True): - """ - Set if this instance should be allowed to be recached. - - """ - self._idmapper_recache_protection = bool(mode) + if pk: + if force or self.at_idmapper_flush(): + self.__class__.__dbclass__.__instance_cache__.pop(pk, None) + else: + self.refresh_from_db() def delete(self, *args, **kwargs): """ @@ -415,7 +426,6 @@ class WeakSharedMemoryModelBase(SharedMemoryModelBase): def _prepare(cls): super(WeakSharedMemoryModelBase, cls)._prepare() cls.__dbclass__.__instance_cache__ = WeakValueDictionary() - cls._idmapper_recache_protection = False class WeakSharedMemoryModel(with_metaclass(WeakSharedMemoryModelBase, SharedMemoryModel)): @@ -429,10 +439,9 @@ class WeakSharedMemoryModel(with_metaclass(WeakSharedMemoryModelBase, SharedMemo def flush_cache(**kwargs): """ - Flush idmapper cache. When doing so the cache will - look for a property `_idmapper_cache_flush_safe` on the - class/subclass instance and only flush if this - is `True`. + Flush idmapper cache. When doing so the cache will fire the + at_idmapper_flush hook to allow the object to optionally handle + its own flushing. Uses a signal so we make sure to catch cascades.