From fa93c70e7ff317a26c3ffea75a5127b9ad62a1af Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 19 Sep 2013 20:41:42 +0200 Subject: [PATCH] First implementation of OOBHandler. --- src/comms/models.py | 4 +- src/server/caches.py | 21 ++----- src/server/oobhandler.py | 125 +++++++++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 62 deletions(-) diff --git a/src/comms/models.py b/src/comms/models.py index ecda0db84..d9f91a67e 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -591,10 +591,10 @@ class Channel(SharedMemoryModel): # get all players connected to this channel and send to them for conn in Channel.objects.get_all_connections(self, online=online): try: - conn.player.msg(msg, senders) + conn.player.msg(msg, from_object=senders) except AttributeError: try: - conn.to_external(msg, senders, from_channel=self) + conn.to_external(msg, from_object=senders, from_channel=self) except Exception: logger.log_trace("Cannot send msg to connection '%s'" % conn) return True diff --git a/src/server/caches.py b/src/server/caches.py index 064d62e0a..b5533f31b 100644 --- a/src/server/caches.py +++ b/src/server/caches.py @@ -107,17 +107,9 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg new_value = field.value_from_object(instance) # try to see if there is a handler on object that should be triggered when saving. handlername = "_at_%s_save" % fieldname - try: - # try-except is about 14 times faster than hasattr in this case - handler = _GA(instance, handlername) - except AttributeError: - handler = None - #hid = hashid(instance, "-%s" % fieldname) - try: - old_value = _GA(instance, _GA(field, "get_cache_name")())#_FIELD_CACHE.get(hid) if hid else None - except AttributeError: - old_value=None + handler = _GA(instance, handlername) if handlername in _GA(instance, '__dict__') else None if callable(handler): + #hid = hashid(instance, "-%s" % fieldname) try: old_value = _GA(instance, _GA(field, "get_cache_name")())#_FIELD_CACHE.get(hid) if hid else None except AttributeError: @@ -127,12 +119,9 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg new_value = handler(new_value, old_value=old_value) # we re-assign this to the field, save() will pick it up from there _SA(instance, fieldname, new_value) - oob_tracker_name = "_track_%s_change" % fieldname - try: - _GA(instance, oob_tracker_name).update(new_value) - except AttributeError: - pass - + trackerhandler = _GA(instance, "_trackerhandler") if "_trackerhandler" in _GA(instance, '__dict__') else None + if trackerhandler: + trackerhandler.update(fieldname, new_value) #if hid: # # update cache # _FIELD_CACHE[hid] = new_value diff --git a/src/server/oobhandler.py b/src/server/oobhandler.py index 64f6675c8..14d4040fd 100644 --- a/src/server/oobhandler.py +++ b/src/server/oobhandler.py @@ -40,54 +40,91 @@ _OOB_TRACKERS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_TRACKERS", # functions return immediately _OOB_FUNCS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_FUNCS", default={}) -class OOBTrackerBase(object): + +class TrackerHandler(object): """ - Base class for OOB Tracker objects. This can be overloaded in settings.to implement - callback functionality. Stored as a property given by the key in _OOB_TRACKERS or - by the value of a class variable property_name. + This object is dynamically assigned to objects whenever one of its fields + are to be tracked. It holds an internal dictionary mapping to the fields + on that object. Each field can be tracked by any number of trackers (each + tied to a different callback). """ - def __init__(self, obj, *args, **kwargs): - self.obj = obj + def __init__(self, obj): + """ + This is initiated and stored on the object as a property _trackerhandler. + """ + self.obj = obj.dbobj + self.ntrackers = 0 + # initiate store only with valid on-object fieldnames + self.tracktargets = dict((key, {}) for key in _GA(_GA(self.obj, "_meta"), "get_all_field_names")()) + + def add(self, fieldname, trackerkey, trackerobj): + """ + Add tracker to the handler. Raises KeyError if fieldname + does not exist. + """ + self.tracktargets[fieldname][trackerkey] = trackerobj + self.ntrackers += 1 + + def remove(self, fieldname, trackerkey, *args, **kwargs): + """ + Remove tracker from handler. Raises KeyError if tracker + is not found. + """ + oobobj = self.tracktargets[fieldname][trackerkey] + try: + oobobj.at_delete(*args, **kwargs) + except Exception: + logger.log_trace() + del oobobj + self.ntrackers -= 1 + if self.ntrackers <= 0: + # if there are no more trackers, clean this handler + del self + + def update(self, fieldname, new_value): + """ + Called by the field when it updates to a new value + """ + for trackerobj in self.tracktargets[fieldname].values(): + try: + trackerobj.update(fieldname, new_value) + except Exception: + logger.log_trace() + +class TrackerBase(object): + """ + Base class for OOB Tracker objects. + """ + def __init__(self, *args, **kwargs): + pass def update(self, *args, **kwargs): "Called by tracked objects" pass def at_remove(self, *args, **kwargs): - "Called when OOB is removed, in case cleanup is needed" + "Called when tracker is removed" pass # Default tracker OOB class -class OOBTracker(OOBTrackerBase): +class OOBTracker(TrackerBase): """ - A OOB object that passively responds whenever - a named database field, property or attribute - changes. This is directly supported by Evennia's - caching mechanism, which looks for hooks stored with - names on the form - _at_db__change - track database field changes - _at_prop__change - track other property changes - _at_attr__change - track Attribute changes - and will call the update() method - - OOBHandler launches this tracking with e.g. - OOBHANDLER.track(obj, "_at_db_key_change", "db_key", "field", sessid) + A OOB object that passively sends data to a stored sessid whenever + a named database field changes. """ - def __init__(self, obj, name, sessid, *args, **kwargs): + def __init__(self, fieldname, sessid, *args, **kwargs): """ - obj - the object to store this hook on name - name of entity to track, such as "db_key" track_type - one of "field", "prop" or "attr" for Database fields, non-database Property or Attribute sessid - sessid of session to report to """ - self.obj = obj - self.name = name + self.fieldname = fieldname self.sessid = sessid def update(self, new_value, *args, **kwargs): "Called by cache when updating the tracked entitiy" SESSIONS.session_from_sessid(self.sessid).msg(oob={"cmdkey":"trackreturn", - "name":self.name, + "name":self.fieldname, "value":new_value}) @@ -161,8 +198,9 @@ class _RepeaterPool(object): def OOB_get_attr_val(caller, attrname): "Get the given attrback from caller" - caller.msg(oob={"cmdkey":"get_attr", "value":to_str(caller.attributes.get(attrname))}) - + caller.msg(oob={"cmdkey":"get_attr", + "name":attrname, + "value":to_str(caller.attributes.get(attrname))}) # Main OOB Handler @@ -199,8 +237,8 @@ class OOBHandler(object): tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage") if tracker_storage: self.oob_tracker_storage = dbunserialize(tracker_storage) - for tracker_key, (obj, prop_name, args, kwargs) in self.oob_tracker_storage.items(): - self.track(obj, tracker_key, *args, **kwargs) + for tracker_key, (obj, sessid, fieldname, args, kwargs) in self.oob_tracker_storage.items(): + self.track(obj, sessid, fieldname, tracker_key, *args, **kwargs) repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage") if repeat_storage: @@ -209,7 +247,7 @@ class OOBHandler(object): self.repeat(caller, func_key, interval, *args, **kwargs) - def track(self, obj, tracker_key, property_name=None, *args, **kwargs): + def track(self, obj, sessid, fieldname, tracker_key, *args, **kwargs): """ Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args, kwargs will be used to initialize the OOB hook before adding @@ -219,30 +257,30 @@ class OOBHandler(object): obj, otherwise tracker_key is ysed as the property name. """ oobclass = _OOB_TRACKERS[tracker_key] # raise traceback if not found - prop_name = property_name or (hasattr(oobclass,"property_name") and oobclass.property_name) or tracker_key - # initialize - oob = oobclass(obj, *args, **kwargs) - _SA(obj, prop_name, oob) + if not "_trackerhandler" in _GA(obj, "__dict__"): + # assign trackerhandler to object + _SA(obj, "_trackerhandler", TrackerHandler(obj)) + # initialize object + oob = oobclass(obj, sessid, fieldname, *args, **kwargs) + _GA(obj, "_trackerhandler").add(oob, fieldname) + # store calling arguments as a pickle for retrieval later - storekey = (pack_dbobj(obj), prop_name) - stored = (obj, prop_name, args, kwargs) + storekey = (pack_dbobj(obj), sessid, fieldname) + stored = (obj, sessid, fieldname, args, kwargs) self.oob_tracker_storage[storekey] = stored - def untrack(self, obj, tracker_key, *args, **kwargs): + def untrack(self, obj, sessid, fieldname, tracker_key, *args, **kwargs): """ Remove the OOB from obj. If oob implements an at_delete hook, this will be called with args, kwargs """ - oobclass = _OOB_TRACKERS[tracker_key] # raise traceback if not found - prop_name = oobclass.property_name if hasattr(oobclass, "property_name") else tracker_key try: # call at_delete hook - _GA(obj, prop_name).at_delete(*args, **kwargs) + _GA(obj, "_trackerhandler").remove(fieldname, tracker_key, *args, **kwargs) except AttributeError: pass - _DA(obj, prop_name) # remove the pickle from storage - store_key = (pack_dbobj(obj), prop_name) + store_key = (pack_dbobj(obj), sessid, fieldname) self.oob_tracker_storage.pop(store_key, None) def track_field(self, obj, sessid, field_name, tracker_key="oobtracker"): @@ -250,7 +288,7 @@ class OOBHandler(object): Shortcut wrapper method for specifically tracking a database field. Uses OOBTracker by default (change tracker_key to redirect) Will create a tracker with a property name that the field cache - expects. + expects """ # all database field names starts with db_* field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name @@ -269,7 +307,6 @@ class OOBHandler(object): oob_tracker_name = "_track_db_value_change" self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name) - def run(self, func_key, *args, **kwargs): """ Retrieve oobfunc from OOB_FUNCS and execute it immediately