First implementation of OOBHandler.

This commit is contained in:
Griatch 2013-09-19 20:41:42 +02:00
parent d74cce4dfe
commit fa93c70e7f
3 changed files with 88 additions and 62 deletions

View file

@ -591,10 +591,10 @@ class Channel(SharedMemoryModel):
# get all players connected to this channel and send to them # get all players connected to this channel and send to them
for conn in Channel.objects.get_all_connections(self, online=online): for conn in Channel.objects.get_all_connections(self, online=online):
try: try:
conn.player.msg(msg, senders) conn.player.msg(msg, from_object=senders)
except AttributeError: except AttributeError:
try: try:
conn.to_external(msg, senders, from_channel=self) conn.to_external(msg, from_object=senders, from_channel=self)
except Exception: except Exception:
logger.log_trace("Cannot send msg to connection '%s'" % conn) logger.log_trace("Cannot send msg to connection '%s'" % conn)
return True return True

View file

@ -107,17 +107,9 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg
new_value = field.value_from_object(instance) new_value = field.value_from_object(instance)
# try to see if there is a handler on object that should be triggered when saving. # try to see if there is a handler on object that should be triggered when saving.
handlername = "_at_%s_save" % fieldname handlername = "_at_%s_save" % fieldname
try: handler = _GA(instance, handlername) if handlername in _GA(instance, '__dict__') else None
# 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
if callable(handler): if callable(handler):
#hid = hashid(instance, "-%s" % fieldname)
try: try:
old_value = _GA(instance, _GA(field, "get_cache_name")())#_FIELD_CACHE.get(hid) if hid else None old_value = _GA(instance, _GA(field, "get_cache_name")())#_FIELD_CACHE.get(hid) if hid else None
except AttributeError: 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) new_value = handler(new_value, old_value=old_value)
# we re-assign this to the field, save() will pick it up from there # we re-assign this to the field, save() will pick it up from there
_SA(instance, fieldname, new_value) _SA(instance, fieldname, new_value)
oob_tracker_name = "_track_%s_change" % fieldname trackerhandler = _GA(instance, "_trackerhandler") if "_trackerhandler" in _GA(instance, '__dict__') else None
try: if trackerhandler:
_GA(instance, oob_tracker_name).update(new_value) trackerhandler.update(fieldname, new_value)
except AttributeError:
pass
#if hid: #if hid:
# # update cache # # update cache
# _FIELD_CACHE[hid] = new_value # _FIELD_CACHE[hid] = new_value

View file

@ -40,54 +40,91 @@ _OOB_TRACKERS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_TRACKERS",
# functions return immediately # functions return immediately
_OOB_FUNCS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_FUNCS", default={}) _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 This object is dynamically assigned to objects whenever one of its fields
callback functionality. Stored as a property given by the key in _OOB_TRACKERS or are to be tracked. It holds an internal dictionary mapping to the fields
by the value of a class variable property_name. 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): def __init__(self, obj):
self.obj = 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): def update(self, *args, **kwargs):
"Called by tracked objects" "Called by tracked objects"
pass pass
def at_remove(self, *args, **kwargs): def at_remove(self, *args, **kwargs):
"Called when OOB is removed, in case cleanup is needed" "Called when tracker is removed"
pass pass
# Default tracker OOB class # Default tracker OOB class
class OOBTracker(OOBTrackerBase): class OOBTracker(TrackerBase):
""" """
A OOB object that passively responds whenever A OOB object that passively sends data to a stored sessid whenever
a named database field, property or attribute a named database field changes.
changes. This is directly supported by Evennia's
caching mechanism, which looks for hooks stored with
names on the form
_at_db_<name>_change - track database field changes
_at_prop_<name>_change - track other property changes
_at_attr_<name>_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)
""" """
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" name - name of entity to track, such as "db_key"
track_type - one of "field", "prop" or "attr" for Database fields, track_type - one of "field", "prop" or "attr" for Database fields,
non-database Property or Attribute non-database Property or Attribute
sessid - sessid of session to report to sessid - sessid of session to report to
""" """
self.obj = obj self.fieldname = fieldname
self.name = name
self.sessid = sessid self.sessid = sessid
def update(self, new_value, *args, **kwargs): def update(self, new_value, *args, **kwargs):
"Called by cache when updating the tracked entitiy" "Called by cache when updating the tracked entitiy"
SESSIONS.session_from_sessid(self.sessid).msg(oob={"cmdkey":"trackreturn", SESSIONS.session_from_sessid(self.sessid).msg(oob={"cmdkey":"trackreturn",
"name":self.name, "name":self.fieldname,
"value":new_value}) "value":new_value})
@ -161,8 +198,9 @@ class _RepeaterPool(object):
def OOB_get_attr_val(caller, attrname): def OOB_get_attr_val(caller, attrname):
"Get the given attrback from caller" "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 # Main OOB Handler
@ -199,8 +237,8 @@ class OOBHandler(object):
tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage") tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage")
if tracker_storage: if tracker_storage:
self.oob_tracker_storage = dbunserialize(tracker_storage) self.oob_tracker_storage = dbunserialize(tracker_storage)
for tracker_key, (obj, prop_name, args, kwargs) in self.oob_tracker_storage.items(): for tracker_key, (obj, sessid, fieldname, args, kwargs) in self.oob_tracker_storage.items():
self.track(obj, tracker_key, *args, **kwargs) self.track(obj, sessid, fieldname, tracker_key, *args, **kwargs)
repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage") repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage")
if repeat_storage: if repeat_storage:
@ -209,7 +247,7 @@ class OOBHandler(object):
self.repeat(caller, func_key, interval, *args, **kwargs) 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, Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args,
kwargs will be used to initialize the OOB hook before adding 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. obj, otherwise tracker_key is ysed as the property name.
""" """
oobclass = _OOB_TRACKERS[tracker_key] # raise traceback if not found 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 if not "_trackerhandler" in _GA(obj, "__dict__"):
# initialize # assign trackerhandler to object
oob = oobclass(obj, *args, **kwargs) _SA(obj, "_trackerhandler", TrackerHandler(obj))
_SA(obj, prop_name, oob) # initialize object
oob = oobclass(obj, sessid, fieldname, *args, **kwargs)
_GA(obj, "_trackerhandler").add(oob, fieldname)
# store calling arguments as a pickle for retrieval later # store calling arguments as a pickle for retrieval later
storekey = (pack_dbobj(obj), prop_name) storekey = (pack_dbobj(obj), sessid, fieldname)
stored = (obj, prop_name, args, kwargs) stored = (obj, sessid, fieldname, args, kwargs)
self.oob_tracker_storage[storekey] = stored 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 Remove the OOB from obj. If oob implements an
at_delete hook, this will be called with args, kwargs 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: try:
# call at_delete hook # call at_delete hook
_GA(obj, prop_name).at_delete(*args, **kwargs) _GA(obj, "_trackerhandler").remove(fieldname, tracker_key, *args, **kwargs)
except AttributeError: except AttributeError:
pass pass
_DA(obj, prop_name)
# remove the pickle from storage # 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) self.oob_tracker_storage.pop(store_key, None)
def track_field(self, obj, sessid, field_name, tracker_key="oobtracker"): 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. Shortcut wrapper method for specifically tracking a database field.
Uses OOBTracker by default (change tracker_key to redirect) Uses OOBTracker by default (change tracker_key to redirect)
Will create a tracker with a property name that the field cache Will create a tracker with a property name that the field cache
expects. expects
""" """
# all database field names starts with db_* # all database field names starts with db_*
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name 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" oob_tracker_name = "_track_db_value_change"
self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name) self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name)
def run(self, func_key, *args, **kwargs): def run(self, func_key, *args, **kwargs):
""" """
Retrieve oobfunc from OOB_FUNCS and execute it immediately Retrieve oobfunc from OOB_FUNCS and execute it immediately