Moved attr_cache to new caching system, activated all attribute updating signals.

This commit is contained in:
Griatch 2013-05-29 18:47:51 +02:00
parent 8202dba596
commit b6383ddab9
6 changed files with 95 additions and 66 deletions

View file

@ -17,11 +17,13 @@ transparently through the decorating TypeClass.
import traceback import traceback
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.db.models.signals import post_init, pre_delete
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
from src.server.caches import get_field_cache, set_field_cache, del_field_cache from src.server.caches import get_field_cache, set_field_cache, del_field_cache
from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache
from src.server.caches import attr_post_init, attr_pre_delete
from src.typeclasses.typeclass import TypeClass from src.typeclasses.typeclass import TypeClass
from src.players.models import PlayerNick from src.players.models import PlayerNick
from src.objects.manager import ObjectManager from src.objects.manager import ObjectManager
@ -53,6 +55,7 @@ _HERE = _("here")
# #
#------------------------------------------------------------ #------------------------------------------------------------
class ObjAttribute(Attribute): class ObjAttribute(Attribute):
"Attributes for ObjectDB objects." "Attributes for ObjectDB objects."
db_obj = models.ForeignKey("ObjectDB") db_obj = models.ForeignKey("ObjectDB")
@ -62,6 +65,10 @@ class ObjAttribute(Attribute):
verbose_name = "Object Attribute" verbose_name = "Object Attribute"
verbose_name_plural = "Object Attributes" verbose_name_plural = "Object Attributes"
# attach the cache handlers for attribute lookup
post_init.connect(attr_post_init, sender=ObjAttribute, dispatch_uid="objattrcache")
pre_delete.connect(attr_pre_delete, sender=ObjAttribute, dispatch_uid="objattrcache")
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Alias # Alias

View file

@ -27,9 +27,12 @@ from django.conf import settings
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.db.models.signals import post_init, pre_delete
from src.server.caches import get_field_cache, set_field_cache, del_field_cache from src.server.caches import get_field_cache, set_field_cache, del_field_cache
from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache
from src.server.caches import attr_post_init, attr_pre_delete
from src.players import manager from src.players import manager
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
@ -74,6 +77,9 @@ class PlayerAttribute(Attribute):
"Define Django meta options" "Define Django meta options"
verbose_name = "Player Attribute" verbose_name = "Player Attribute"
post_init.connect(attr_post_init, sender=PlayerAttribute, dispatch_uid="playerattrcache")
pre_delete.connect(attr_pre_delete, sender=PlayerAttribute, dispatch_uid="playerattrcache")
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Player Nicks # Player Nicks

View file

@ -26,6 +26,9 @@ Common examples of uses of Scripts:
""" """
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models.signals import post_init, pre_delete
from src.server.caches import attr_post_init, attr_pre_delete
from src.typeclasses.models import Attribute, TypedObject from src.typeclasses.models import Attribute, TypedObject
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from src.scripts.manager import ScriptManager from src.scripts.manager import ScriptManager
@ -47,6 +50,9 @@ class ScriptAttribute(Attribute):
verbose_name = "Script Attribute" verbose_name = "Script Attribute"
verbose_name_plural = "Script Attributes" verbose_name_plural = "Script Attributes"
# attach cache handlers for attribute lookup
post_init.connect(attr_post_init, sender=ScriptAttribute, dispatch_uid="scriptattrcache")
pre_delete.connect(attr_pre_delete, sender=ScriptAttribute, dispatch_uid="scriptattrcache")
#------------------------------------------------------------ #------------------------------------------------------------
# #

View file

@ -2,6 +2,8 @@
Central caching module. Central caching module.
""" """
from django.core.cache import get_cache
#from django.db.models.signals import pre_save, pre_delete, post_init
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.utils.utils import uses_database, to_str from src.utils.utils import uses_database, to_str
@ -9,13 +11,27 @@ _GA = object.__getattribute__
_SA = object.__setattr__ _SA = object.__setattr__
_DA = object.__delattr__ _DA = object.__delattr__
#
# Open handles to the caches
#
_FIELD_CACHE = get_cache("field_cache")
_ATTR_CACHE = get_cache("attr_cache")
# make sure caches are empty at startup
_FIELD_CACHE.clear()
_ATTR_CACHE.clear()
#
# Cache key hash generation
#
if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6.4': if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6.4':
# mysql <5.6.4 don't support millisecond precision # mysql <5.6.4 don't support millisecond precision
_DATESTRING = "%Y:%m:%d-%H:%M:%S:000000" _DATESTRING = "%Y:%m:%d-%H:%M:%S:000000"
else: else:
_DATESTRING = "%Y:%m:%d-%H:%M:%S:%f" _DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
def hashid(obj, suffix=""): def hashid(obj, suffix=""):
""" """
Returns a per-class unique that combines the object's Returns a per-class unique that combines the object's
@ -49,36 +65,28 @@ def hashid(obj, suffix=""):
return to_str(hid) return to_str(hid)
# signal handlers #
# Cache callback handlers
#
from django.core.cache import get_cache # Field cache - makes sure to cache all database fields when
#from django.db.models.signals import pre_save, pre_delete, post_init # they are saved, no matter from where.
# field cache # callback to pre_save signal (connected in src.server.server)
def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
_FIELD_CACHE = get_cache("field_cache")
if not _FIELD_CACHE:
raise RuntimeError("settings.CACHE does not contain a 'field_cache' entry!")
# callback before saving an object
def field_pre_save(sender, **kwargs):
""" """
Called at the beginning of the save operation. The save method Called at the beginning of the save operation. The save method
must be called with the update_fields keyword in order to must be called with the update_fields keyword in order to
""" """
global _FIELD_CACHE if raw:
return
if kwargs.pop("raw", False): return if update_fields:
instance = kwargs.pop("instance")
fields = kwargs.pop("update_fields", None)
if fields:
# this is a list of strings at this point. We want field objects # this is a list of strings at this point. We want field objects
fields = (instance._meta.get_field_by_name(field)[0] for field in fields) update_fields = (instance._meta.get_field_by_name(field)[0] for field in update_fields)
else: else:
# meta.fields are already field objects # meta.fields are already field objects
fields = instance._meta.fields update_fields = instance._meta.fields
for field in fields: for field in update_fields:
fieldname = field.name fieldname = field.name
new_value = field.value_from_object(instance) new_value = field.value_from_object(instance)
handlername = "_%s_handler" % fieldname handlername = "_%s_handler" % fieldname
@ -98,39 +106,34 @@ def field_pre_save(sender, **kwargs):
# update cache # update cache
_FIELD_CACHE.set(hid, new_value) _FIELD_CACHE.set(hid, new_value)
# goes into server: # Attr cache - caching the attribute objects related to a given object to
#pre_save.connect(field_pre_save, dispatch_uid="fieldcache") # avoid lookups more than necessary (this makes Attributes en par in speed
# to any property).
# connected to post_init signal (connected in respective Attribute model)
def attr_post_init(sender, instance=None, **kwargs):
"Called when attribute is created or retrieved in connection with obj."
#print "attr_post_init:", instance, instance.db_obj, instance.db_key
hid = hashid(_GA(instance, "db_obj"), "-%s" % _GA(instance, "db_key"))
if hid:
_ATTR_CACHE.set(hid, sender)
# connected to pre_delete signal (connected in respective Attribute model)
def attr_pre_delete(sender, instance=None, **kwargs):
"Called when attribute is deleted (del_attribute)"
#print "attr_pre_delete:", instance, instance.db_obj, instance.db_key
hid = hashid(_GA(instance, "db_obj"), "-%s" % _GA(instance, "db_key"))
if hid:
#print "attr_pre_delete:", _GA(instance, "db_key")
_ATTR_CACHE.delete(hid)
# access method
def get_attr_cache(obj, attrname):
"Called by get_attribute"
hid = hashid(obj, "-%s" % attrname)
_ATTR_CACHE.delete(hid)
return hid and _ATTR_CACHE.get(hid, None) or None
## attr cache - caching the attribute objects related to a given object to
## avoid lookups more than necessary (this makes attributes en par in speed
## to any property). The signal is triggered by the Attribute itself when it
## is created or deleted (it holds a reference to the object)
#
#_ATTR_CACHE = get_cache("attr_cache")
#if not _ATTR_CACHE:
# raise RuntimeError("settings.CACHE does not contain an 'attr_cache' entry!")
#
#def attr_post_init(sender, **kwargs):
# "Called when attribute is created or retrieved in connection with obj."
# hid = hashid(sender.db_obj, "-%s" % sender.db_key)
# _ATTR_CACHE.set(hid, sender)
#def attr_pre_delete(sender, **kwargs):
# "Called when attribute is deleted (del_attribute)"
# hid = hashid(sender.db_obj, "-%s" % sender.db_key)
# _ATTR_CACHE.delete(hid)
#
### goes into server:
#from src.objects.models import ObjAttribute
#from src.scripts.models import ScriptAttribute
#from src.players.models import PlayerAttribute
#post_init.connect(attr_post_init, sender=ObjAttribute, dispatch_uid="objattrcache")
#post_init.connect(attr_post_init, sender=ScriptAttribute, dispatch_uid="scriptattrcache")
#post_init.connect(attr_post_init, sender=PlayerAttribute, dispatch_uid="playerattrcache")
#pre_delete.connect(attr_pre_delete, sender=ObjAttribute, dispatch_uid="objattrcache")
#pre_delete.connect(attr_pre_delete, sender=ScriptAttribute, dispatch_uid="scriptattrcache")
#pre_delete.connect(attr_pre_delete, sender=PlayerAttribute, dispatch_uid="playerattrcache")
#
#
## property cache - this doubles as a central cache and as a way ## property cache - this doubles as a central cache and as a way
## to trigger oob on such changes. ## to trigger oob on such changes.
# #
@ -456,8 +459,8 @@ def del_prop_cache(obj, name):
pass pass
def flush_prop_cache(obj=None): def flush_prop_cache(obj=None):
pass pass
def get_attr_cache(obj, attrname): #def get_attr_cache(obj, attrname):
return None # return None
def set_attr_cache(obj, attrname, attrobj): def set_attr_cache(obj, attrname, attrobj):
pass pass
def del_attr_cache(obj, attrname): def del_attr_cache(obj, attrname):

View file

@ -103,7 +103,7 @@ class Attribute(SharedMemoryModel):
# Attribute Database Model setup # Attribute Database Model setup
# #
# #
# These databse fields are all set using their corresponding properties, # These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix. # named same as the field, but withtout the db_* prefix.
db_key = models.CharField('key', max_length=255, db_index=True) db_key = models.CharField('key', max_length=255, db_index=True)
@ -933,10 +933,11 @@ class TypedObject(SharedMemoryModel):
if not get_attr_cache(self, attribute_name): if not get_attr_cache(self, attribute_name):
attrib_obj = _GA(self, "_attribute_class").objects.filter( attrib_obj = _GA(self, "_attribute_class").objects.filter(
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if attrib_obj: if not attrib_obj:
set_attr_cache(self, attribute_name, attrib_obj[0])
else:
return False return False
#set_attr_cache(self, attribute_name, attrib_obj[0])
#else:
# return False
return True return True
def set_attribute(self, attribute_name, new_value=None, lockstring=""): def set_attribute(self, attribute_name, new_value=None, lockstring=""):
@ -953,6 +954,7 @@ class TypedObject(SharedMemoryModel):
types checked by secureattr are 'attrread','attredit','attrcreate'. types checked by secureattr are 'attrread','attredit','attrcreate'.
""" """
attrib_obj = get_attr_cache(self, attribute_name) attrib_obj = get_attr_cache(self, attribute_name)
print "set_attribute:", attribute_name, attrib_obj
if not attrib_obj: if not attrib_obj:
attrclass = _GA(self, "_attribute_class") attrclass = _GA(self, "_attribute_class")
# check if attribute already exists. # check if attribute already exists.
@ -975,7 +977,7 @@ class TypedObject(SharedMemoryModel):
flush_attr_cache(self) flush_attr_cache(self)
self.delete() self.delete()
raise IntegrityError("Attribute could not be saved - object %s was deleted from database." % self.key) raise IntegrityError("Attribute could not be saved - object %s was deleted from database." % self.key)
set_attr_cache(self, attribute_name, attrib_obj) #set_attr_cache(self, attribute_name, attrib_obj)
def get_attribute_obj(self, attribute_name, default=None): def get_attribute_obj(self, attribute_name, default=None):
""" """
@ -987,7 +989,7 @@ class TypedObject(SharedMemoryModel):
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if not attrib_obj: if not attrib_obj:
return default return default
set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here #set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here
return attrib_obj[0] return attrib_obj[0]
return attrib_obj return attrib_obj
@ -1006,7 +1008,7 @@ class TypedObject(SharedMemoryModel):
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if not attrib_obj: if not attrib_obj:
return default return default
set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here #set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here
return attrib_obj[0].value return attrib_obj[0].value
return attrib_obj.value return attrib_obj.value
@ -1023,7 +1025,7 @@ class TypedObject(SharedMemoryModel):
db_obj=self, db_key__iexact=attribute_name) db_obj=self, db_key__iexact=attribute_name)
if not attrib_obj: if not attrib_obj:
raise AttributeError raise AttributeError
set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here #set_attr_cache(self, attribute_name, attrib_obj[0]) #query is first evaluated here
return attrib_obj[0].value return attrib_obj[0].value
return attrib_obj.value return attrib_obj.value

View file

@ -91,7 +91,12 @@ class SharedMemoryModelBase(ModelBase):
super(SharedMemoryModelBase, cls)._prepare() super(SharedMemoryModelBase, cls)._prepare()
def __init__(cls, *args, **kwargs): def __init__(cls, *args, **kwargs):
"Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key" """
Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key
This wrapper happens on the class level, so there is no overhead when creating objects. If a class
already has a wrapper of the given name, the automatic creation is skipped. Note: Remember to
document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise.
"""
super(SharedMemoryModelBase, cls).__init__(*args, **kwargs) super(SharedMemoryModelBase, cls).__init__(*args, **kwargs)
def create_wrapper(cls, fieldname, wrappername): def create_wrapper(cls, fieldname, wrappername):
"Helper method to create property wrappers with unique names (must be in separate call)" "Helper method to create property wrappers with unique names (must be in separate call)"
@ -114,7 +119,7 @@ class SharedMemoryModelBase(ModelBase):
wrappername = fieldname == "id" and "dbref" or fieldname.replace("db_", "") wrappername = fieldname == "id" and "dbref" or fieldname.replace("db_", "")
if not hasattr(cls, wrappername): if not hasattr(cls, wrappername):
# make sure not to overload manually created wrappers on the model # make sure not to overload manually created wrappers on the model
print "wrapping %s -> %s" % (fieldname, wrappername) #print "wrapping %s -> %s" % (fieldname, wrappername)
create_wrapper(cls, fieldname, wrappername) create_wrapper(cls, fieldname, wrappername)
class SharedMemoryModel(Model): class SharedMemoryModel(Model):