First version of OOBHandler put together. Might still have to rework it since it cannot properly handle multiple trackers tracking a single field on a given object.

This commit is contained in:
Griatch 2013-09-14 23:18:36 +02:00
parent 4a5de04956
commit d74cce4dfe
7 changed files with 293 additions and 288 deletions

View file

@ -113,6 +113,10 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg
except AttributeError: except AttributeError:
handler = None handler = None
#hid = hashid(instance, "-%s" % fieldname) #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):
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
@ -123,8 +127,12 @@ 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)
if instance and hasattr(instance, "oobhandler"): oob_tracker_name = "_track_%s_change" % fieldname
_GA(instance, "oobhandler").update("fieldset", fieldname, old_value, new_value) try:
_GA(instance, oob_tracker_name).update(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

@ -1,319 +1,308 @@
""" """
-- OBS - OOB is not yet functional in Evennia. Don't use this module -- OOBHandler - Out Of Band Handler
OOB - Out-of-band central handler The OOBHandler is called directly by out-of-band protocols. It supplies three
pieces of functionality:
This module presents a central API for requesting data from objects in function execution - the oob protocol can execute a function directly on
Evennia via OOB negotiation. It is meant specifically to be imported the server. Only functions specified in settings.OOB_PLUGIN_MODULE.OOB_FUNCS
and used by the module defined in settings.OOB_FUNC_MODULE. are valid for this use.
repeat func execution - the oob protocol can request a given function be executed repeatedly
at a regular interval.
tracking - the oob protocol can request Evennia to track changes to fields/properties on
objects, as well as changes in Attributes. This is done by dynamically adding
tracker-objects on entities. The behaviour of those objects can be customized
via settings.OOB_PLUGIN_MODULE.OOB_TRACKERS.
Import src.server.oobhandler and use the methods in OOBHANDLER. oob functions have the following call signature:
function(caller, *args, **kwargs)
The actual client protocol (MSDP, GMCP, whatever) does not matter at
this level, serialization is assumed to happen at the protocol level
only.
This module offers the following basic functionality:
track_passive - retrieve field, property, db/ndb attribute from an object, then continue reporting
changes henceforth. This is done efficiently and on-demand using hooks. This should be
used preferentially since it's very resource efficient.
track_active - this is an active reporting mechanism making use of a Script. This should normally
only be used if:
1) you want changes to be reported SLOWER than the actual rate of update (such
as only wanting to show an average of change over time)
2) the data you are reporting is NOT stored as a field/property/db/ndb on an object (such
as some sort of server statistic calculated on the fly).
Trivial operations such as get/setting individual properties one time is best done directly from
the OOB_MODULE functions.
Examples of call from OOB_FUNC_MODULE:
from src.server.oobhandler import OOBHANDLER
def track_desc(session, *args, **kwargs):
"Sets up a passive watch for the desc attribute on session object"
if session.player:
char = session.player.get_puppet(session.sessid)
if char:
OOBHANDLER.track_passive(session, char, "desc", entity="db")
# to start off we return the value once
return char.db.desc
What is passed around is a dictionary (pickled to a string) on the form
{oobfunction: ((arg1,arg2,...),{kwarg1:val,kwarg2:val}), oobfunction2: ... }
oob trackers should inherit from the OOBTracker class in this
module and implement a minimum of the same functionality.
""" """
from django.conf import settings from django.conf import settings
from collections import defaultdict from src.server.models import ServerConfig
from src.scripts.objects import ScriptDB from src.server.sessionhandler import SESSIONS
from src.scripts.script import Script from src.scripts.scripts import Script
from src.server import caches from src.create import create_script
from src.server.caches import hashid from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj
from src.utils import logger, create from src.utils import logger
from src.utils.utils import variable_from_module from src.utils.utils import variable_from_module, to_str
# get the custom function map of available oob functions _SA = object.__setattr__
_OOB_FUNCMAP = variable_from_module(settings.OOB_FUNC_MODULE, "OOB_FUNC_MAP", default={}) _GA = object.__getattribute__
_DA = object.__delattribute__
# trackers track property changes and keep returning until they are removed
_OOB_TRACKERS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_TRACKERS", default={})
# functions return immediately
_OOB_FUNCS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_FUNCS", default={})
class OOBTrackerBase(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.
"""
def __init__(self, obj, *args, **kwargs):
self.obj = obj
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"
pass
# Default tracker OOB class
class OOBTracker(OOBTrackerBase):
"""
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_<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):
"""
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.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,
"value":new_value})
class _TrackerPool(object): class _RepeaterPool(object):
""" """
This maintains a pool of __OOBTracker scripts, ordered by interval This maintains a pool of _RepeaterScript scripts, ordered one per interval. It
will automatically cull itself once a given interval's script has no more
subscriptions.
""" """
def __init__(self):
self.trackerscripts = {}
def add(self, obj, interval, oobkey):
"""
Add a new tracking
"""
if interval not in self.trackerscripts:
# if no existing interval exists, create new script to fill the gap
new_tracker = create.script(_OOBTracker, interval=interval)
self.trackerscripts[interval] = new_tracker
self.trackerscripts[interval].subscribe(obj, oobkey)
class _RepeaterScript(Script):
class _OOBTracker(Script):
""" """
Active tracker script Repeating script for triggering OOB functions. Maintained in the pool.
""" """
def at_script_creation(self): def at_script_creation(self):
"Called when script is initialized" "Called when script is initialized"
self.key = "oob_func" self.key = "oob_func"
self.desc = "OOB functionality script" self.desc = "OOB functionality script"
self.persistent = False #oob scripts should always be non-persistent self.persistent = False #oob scripts should always be non-persistent
self.db.subscriptions = {} self.ndb.subscriptions = {}
def at_repeat(self): def at_repeat(self):
""" """
Calls subscriptions every self.interval seconds Calls subscriptions every self.interval seconds
""" """
for obj, oobkey in self.db.subscriptions.values(): for (func_key, caller, interval, args, kwargs) in self.ndb.subscriptions.values():
try: try:
obj.oobhandler.execute_func() _OOB_FUNCS[func_key](caller, *args, **kwargs)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
def subscribe(self, subscriber, oobkey, **kwargs): def subscribe(self, store_key, caller, func_key, interval, *args, **kwargs):
""" """
Sign up a subscriber to this oobfunction. Subscriber is Sign up a subscriber to this oobfunction. Subscriber is
a database object with a dbref. a database object with a dbref.
""" """
self.db.subscriptions[subscriber.dbid] = (subscriber.dbobj, oobkey, kwargs) self.ndb.subscriptions[store_key] = (func_key, caller, interval, args, kwargs)
def unsubscribe(self, subscriber): def unsubscribe(self, store_key):
""" """
Unsubscribe from oobfunction. Returns True if removal was Unsubscribe from oobfunction. Returns True if removal was
successful, False otherwise successful, False otherwise
""" """
removed = self.db.subscriptions.pop(subscriber.dbid, False) self.ndb.subscriptions.pop(store_key, None)
return True if removed else False
def __init__(self):
self.scripts = {}
def add(self, store_key, caller, func_key, interval, *args, **kwargs):
"""
Add a new tracking
"""
if interval not in self.scripts:
# if no existing interval exists, create new script to fill the gap
new_tracker = create_script(self._RepeaterScript, key="oob_repeater_%is" % interval, interval=interval)
self.scripts[interval] = new_tracker
self.scripts[interval].subscribe(store_key, caller, func_key, interval, *args, **kwargs)
def remove(self, store_key, interval):
"""
Remove tracking
"""
if interval in self.scripts:
self.scripts[interval].unsubscribe(store_key)
if len(self.scripts[interval].ndb.subscriptions) == 0:
# no more subscriptions for this interval. Clean out the script.
self.scripts[interval].stop()
# Default OOB funcs
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))})
# Main OOB Handler
class OOBHandler(object): class OOBHandler(object):
""" """
Out-of-band handler. Should be initialized on each model that should be possible to track. The OOBHandler maintains all dynamic on-object oob hooks. It will store the
Tracking will apply creation instructions and and re-apply them at a server reload (but not after
a server shutdown)
""" """
def __init__(self, obj): def __init__(self):
"initialize the handler with the object it is stored on" """
self.obj = obj Initialize handler
self.tracked = defaultdict(dict) """
self.oobstrings = "" self.oob_tracker_storage = {}
self.oob_repeat_storage = {}
self.oob_tracker_pool = _RepeaterPool()
def parse_commanddict(self, dic): def save(self):
""" """
The command dict is on the form Save the command_storage as a serialized string into a temporary
{functionname:((args), {kwargs}), ...} ServerConf field
It is stored in text form as a pickle.
""" """
if self.oob_tracker_storage:
ServerConfig.objects.conf(key="oob_tracker_storage", value=dbserialize(self.oob_tracker_storage))
if self.oob_repeat_storage:
ServerConfig.objects.conf(key="oob_repeat_storage", value=dbserialize(self.oob_repeat_storage))
def restore(self):
"""
Restore the command_storage from database and re-initialize the handler from storage.. This is
only triggered after a server reload, not after a shutdown-restart
"""
# load stored command instructions and use them to re-initialize handler
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)
repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage")
if repeat_storage:
self.oob_repeat_storage = dbunserialize(repeat_storage)
for func_key, (caller, func_key, interval, args, kwargs) in self.oob_repeat_storage.items():
self.repeat(caller, func_key, interval, *args, **kwargs)
def track(self, obj, tracker_key, property_name=None, *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
it to obj.
If property_key is not given, but the OOB has a class property property_name, this
will be used as the property name when assigning the OOB to
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)
# store calling arguments as a pickle for retrieval later
storekey = (pack_dbobj(obj), prop_name)
stored = (obj, prop_name, args, kwargs)
self.oob_tracker_storage[storekey] = stored
def _make_hash(self, callback_key, hashkey): def untrack(self, obj, tracker_key, *args, **kwargs):
""" """
create an id-hash for storage Remove the OOB from obj. If oob implements an
""" at_delete hook, this will be called with args, kwargs
return "%s-%s" % (callback_key, hashkey)
def track(self, callback_key, hashkey, interval=None, **kwargs):
"""
Access method - start tracking given changes on this object
oobkey - available function key mapped in OOB_FUNC_MODULE.OOB_FUNC_MAP
interval - if None, updating will happen on-demand, only when appropriate callbacks are triggered.
if int > 0, the tracker will actively call oobfunc at this interval. Usually, on-demand
updating is preferred for efficiency reasons.
other kwargs will be passed to oob function given by oobkey at run-time along with other on-the-fly kwargs.
"""
hid = self._make_hash(callback_key, hashkey)
if interval:
_OOBTrackPool.add(self, interval, hid)
self.tracked[hid] = kwargs
def update_tracked(self, callback_key, hashkey, **kwargs):
"""
Called by tracked systems when they update
"""
hid = self._make_hash(callback_key, hashkey)
if hid in self.tracked:
tkwargs = self.tracked[hid]
kwargs.update(tkwargs)
self.execute_func(oobkey, **kwargs)
def execute_func(self, callback_key, hashkey, **kwargs):
"""
This is called from the outside to crank the oob mechanism manually
""" """
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:
_OOB_FUNC_MAP[callback_key](self.obj, hashkey, **kwargs) # call at_delete hook
except Exception: _GA(obj, prop_name).at_delete(*args, **kwargs)
logger.log_trace() except AttributeError:
class OOBhandler(object):
"""
Main Out-of-band handler
"""
def __init__(self, obj):
"initialization"
self.obj = obj
self.track_passive_subs = defaultdict(dict)
scripts = ScriptDB.objects.filter(db_key__startswith="oob_tracking_")
self.track_active_subs = dict((s.interval, s) for s in scripts)
# set reference on caches module
caches._OOB_HANDLER = self
def _init_func(self):
"""
Initialize the
"""
def track_passive(self, oobkey, tracker, tracked, entityname, callback=None, mode="db", *args, **kwargs):
"""
Passively track changes to an object property,
attribute or non-db-attribute. Uses cache hooks to
do this on demand, without active tracking.
tracker - object who is tracking
tracked - object being tracked
entityname - field/property/attribute/ndb name to watch
function - function object to call when entity update. When entitye <key>
is updated, this function will be called with called
with function(obj, entityname, new_value, *args, **kwargs)
*args - additional, optional arguments to send to function
mode (keyword) - the type of entity to track. One of
"property", "db", "ndb" or "custom" ("property" includes both
changes to database fields and cached on-model properties)
**kwargs - additional, optionak keywords to send to function
Only entities that are being -cached- can be tracked. For custom
on-typeclass properties, a custom hook needs to be created, calling
the update() function in this module whenever the tracked entity changes.
"""
# always store database object (in case typeclass changes along the way)
try: tracker = tracker.dbobj
except AttributeError: pass
try: tracked = tracked.dbobj
except AttributeError: pass
def default_callback(tracker, tracked, entityname, new_val, *args, **kwargs):
"Callback used if no function is supplied"
pass pass
_DA(obj, prop_name)
# remove the pickle from storage
store_key = (pack_dbobj(obj), prop_name)
self.oob_tracker_storage.pop(store_key, None)
thid = hashid(tracked) def track_field(self, obj, sessid, field_name, tracker_key="oobtracker"):
if not thid:
return
oob_call = (function, oobkey, tracker, tracked, entityname, args, kwargs)
if thid not in self.track_passive_subs:
if mode in ("db", "ndb", "custom"):
caches.register_oob_update_hook(tracked, entityname, mode=mode)
elif mode == "property":
# track property/field. We must first determine which cache to use.
if hasattr(tracked, 'db_%s' % entityname.lstrip("db_")):
hid = caches.register_oob_update_hook(tracked, entityname, mode="field")
else:
hid = caches.register_oob_update_hook(tracked, entityname, mode="property")
if not self.track_pass_subs[hid][entityname]:
self.track_pass_subs[hid][entityname] = {tracker:oob_call}
else:
self.track_passive_subs[hid][entityname][tracker] = oob_call
def untrack_passive(self, tracker, tracked, entityname, mode="db"):
""" """
Remove passive tracking from an object's entity. Shortcut wrapper method for specifically tracking a database field.
mode - one of "property", "db", "ndb" or "custom" Uses OOBTracker by default (change tracker_key to redirect)
Will create a tracker with a property name that the field cache
expects.
""" """
try: tracked = tracked.dbobj # all database field names starts with db_*
except AttributeError: pass field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
oob_tracker_name = "_track_%s_change" % field_name # field cache looks for name on this form
self.track(obj, tracker_key, field_name, sessid, property_name=oob_tracker_name)
thid = hashid(tracked) def track_attribute(self, obj, sessid, attr_name, tracker_key="oobtracker"):
if not thid:
return
if len(self.track_passive_subs[thid][entityname]) == 1:
if mode in ("db", "ndb", "custom"):
caches.unregister_oob_update_hook(tracked, entityname, mode=mode)
elif mode == "property":
if hasattr(self.obj, 'db_%s' % entityname.lstrip("db_")):
caches.unregister_oob_update_hook(tracked, entityname, mode="field")
else:
caches.unregister_oob_update_hook(tracked, entityname, mode="property")
try: del self.track_passive_subs[thid][entityname][tracker]
except (KeyError, TypeError): pass
def update(self, hid, entityname, new_val):
""" """
This is called by the caches when the object when its Shortcut wrapper method for specifically tracking the changes of an
property/field/etc is updated, to inform the oob handler and Attribute on an object. Will create a tracker on the Attribute Object and
all subscribing to this particular entity has been updated name in a way the Attribute expects.
with new_val.
""" """
# tell all tracking objects of the update # get the attribute object if we can
for tracker, oob in self.track_passive_subs[hid][entityname].items(): attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
if attrobj:
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
using *args and **kwargs
"""
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
try: try:
# function(oobkey, tracker, tracked, entityname, new_value, *args, **kwargs) oobfunc(*args, **kwargs)
oob[0](tracker, oob[1], oob[2], new_val, *oob[3], **oob[4])
except Exception: except Exception:
logger.log_trace() logger.log_trace()
# Track (active/proactive tracking) def repeat(self, caller, func_key, interval=20, *args, **kwargs):
# creating and storing tracker scripts
def track_active(self, oobkey, func, interval=30, *args, **kwargs):
""" """
Create a tracking, re-use script with same interval if available, Start a repeating action. Every interval seconds,
otherwise create a new one. the oobfunc corresponding to func_key is called with
args and kwargs.
args:
oobkey - interval-unique identifier needed for removing tracking later
func - function to call at interval seconds
(all other args become argjs into func)
keywords:
interval (default 30s) - how often to update tracker
(all other kwargs become kwargs into func)
""" """
if interval in self.track_active_subs: if not func_key in _OOB_FUNCS:
# tracker with given interval found. Add to its subs raise KeyError("%s is not a valid OOB function name.")
self.track_active_subs[interval].track(oobkey, func, *args, **kwargs) store_key = (pack_dbobj(caller), func_key, interval)
else: # prepare to store
# create new tracker with given interval self.oob_repeat_storage[store_key] = (caller, func_key, interval, args, kwargs)
new_tracker = create.create_script(_OOBTracker, oobkey="oob_tracking_%i" % interval, interval=interval) self.oob_tracker_pool.add(store_key, caller, func_key, interval, *args, **kwargs)
new_tracker.track(oobkey, func, *args, **kwargs)
self.track_active_subs[interval] = new_tracker
def untrack_active(self, oobkey, interval): def unrepeat(self, caller, func_key, interval=20):
""" """
Remove tracking for a given interval and oobkey Stop a repeating action
""" """
tracker = self.track_active_subs.get(interval) store_key = (pack_dbobj(caller), func_key, interval)
if tracker: self.oob_tracker_pool.remove(store_key, interval)
tracker.untrack(oobkey) self.oob_repeat_storage.pop(store_key, None)
# handler object
OOBHANDLER = OOBhandler()
# access object
OOB_HANDLER = OOBHandler()

View file

@ -22,9 +22,9 @@ _GA = object.__getattribute__
_ObjectDB = None _ObjectDB = None
# load optional out-of-band function module # load optional out-of-band function module
OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE OOB_PLUGIN_MODULE = settings.OOB_PLUGIN_MODULE
if OOB_FUNC_MODULE: if OOB_PLUGIN_MODULE:
OOB_FUNC_MODULE = utils.mod_import(settings.OOB_FUNC_MODULE) OOB_PLUGIN_MODULE = utils.mod_import(settings.OOB_PLUGIN_MODULE)
# i18n # i18n
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -206,7 +206,7 @@ class ServerSession(Session):
data = {"get_hp": ("oob_get_hp, [], {}), data = {"get_hp": ("oob_get_hp, [], {}),
"update_counter", ("counter", ["counter1"], {"now":True}) } "update_counter", ("counter", ["counter1"], {"now":True}) }
All function names must be defined in settings.OOB_FUNC_MODULE. Each All function names must be defined in settings.OOB_PLUGIN_MODULE. Each
function will be called with the oobkey and a back-reference to this session function will be called with the oobkey and a back-reference to this session
as their first two arguments. as their first two arguments.
""" """
@ -215,14 +215,14 @@ class ServerSession(Session):
for oobkey, functuple in data.items(): for oobkey, functuple in data.items():
# loop through the data, calling available functions. # loop through the data, calling available functions.
func = OOB_FUNC_MODULE.__dict__.get(functuple[0]) func = OOB_PLUGIN_MODULE.__dict__.get(functuple[0])
if func: if func:
try: try:
outdata[functuple[0]] = func(oobkey, self, *functuple[1], **functuple[2]) outdata[functuple[0]] = func(oobkey, self, *functuple[1], **functuple[2])
except Exception: except Exception:
logger.log_trace() logger.log_trace()
else: else:
logger.log_errmsg("oob_data_in error: funcname '%s' not found in OOB_FUNC_MODULE." % functuple[0]) logger.log_errmsg("oob_data_in error: funcname '%s' not found in OOB_PLUGIN_MODULE." % functuple[0])
if outdata: if outdata:
# we have a direct result - send it back right away # we have a direct result - send it back right away
self.oob_data_out(outdata) self.oob_data_out(outdata)

View file

@ -21,9 +21,6 @@ try:
except ImportError: except ImportError:
import pickle import pickle
dumps = lambda data: to_str(pickle.dumps(data, pickle.HIGHEST_PROTOCOL))
loads = lambda data: pickle.loads(to_str(data))
# delayed imports # delayed imports
_PlayerDB = None _PlayerDB = None
_ServerSession = None _ServerSession = None
@ -327,6 +324,12 @@ class ServerSessionHandler(SessionHandler):
""" """
return len(set(session.uid for session in self.sessions.values() if session.logged_in)) return len(set(session.uid for session in self.sessions.values() if session.logged_in))
def session_from_sessid(self, sessid):
"""
Return session based on sessid, or None if not found
"""
return self.sessions.get(sessid)
def session_from_player(self, player, sessid): def session_from_player(self, player, sessid):
""" """
Given a player and a session id, return the actual session object Given a player and a session id, return the actual session object

View file

@ -196,9 +196,10 @@ PORTAL_SERVICES_PLUGIN_MODULES = []
# Module holding MSSP meta data. This is used by MUD-crawlers to determine # Module holding MSSP meta data. This is used by MUD-crawlers to determine
# what type of game you are running, how many players you have etc. # what type of game you are running, how many players you have etc.
MSSP_META_MODULE = "" MSSP_META_MODULE = ""
# Module holding server-side custom functions for out-of-band protocols to call. # Module holding OOB (Out of Band) hook objects. This allows for customization
# Note that OOB_ENABLED must be True for this to be used. # and expansion of which hooks OOB protocols are allowed to call on the server
OOB_FUNC_MODULE = "" # Not yet available in Evennia - do not use! # protocols for attaching tracker hooks for when various object field change
OOB_PLUGIN_MODULE = ""
# Tuple of modules implementing lock functions. All callable functions # Tuple of modules implementing lock functions. All callable functions
# inside these modules will be available as lock functions. # inside these modules will be available as lock functions.
LOCK_FUNC_MODULES = ("src.locks.lockfuncs",) LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)

View file

@ -206,7 +206,11 @@ class Attribute(SharedMemoryModel):
self.no_cache = False self.no_cache = False
self.db_value = to_store self.db_value = to_store
self.save() self.save()
self.at_set(self.cached_value)
try:
self._track_db_value_change.update(self.cached_value)
except AttributeError:
pass
#@value.deleter #@value.deleter
def __value_del(self): def __value_del(self):

View file

@ -184,7 +184,7 @@ class _SaverSet(_SaverMutable, MutableSet):
# serialization helpers # serialization helpers
# #
def _pack_dbobj(item): def pack_dbobj(item):
""" """
Check and convert django database objects to an internal representation. Check and convert django database objects to an internal representation.
This either returns the original input item or a tuple ("__packed_dbobj__", key, creation_time, id) This either returns the original input item or a tuple ("__packed_dbobj__", key, creation_time, id)
@ -241,7 +241,7 @@ def to_pickle(data):
return item.__class__([process_item(val) for val in item]) return item.__class__([process_item(val) for val in item])
except (AttributeError, TypeError): except (AttributeError, TypeError):
return [process_item(val) for val in item] return [process_item(val) for val in item]
return _pack_dbobj(item) return pack_dbobj(item)
return process_item(data) return process_item(data)
@transaction.autocommit @transaction.autocommit