2013-01-03 09:18:49 +01:00
|
|
|
"""
|
2013-01-07 15:47:41 +01:00
|
|
|
-- OBS - OOB is not yet functional in Evennia. Don't use this module --
|
|
|
|
|
|
2013-01-03 09:18:49 +01:00
|
|
|
OOB - Out-of-band central handler
|
|
|
|
|
|
|
|
|
|
This module presents a central API for requesting data from objects in
|
|
|
|
|
Evennia via OOB negotiation. It is meant specifically to be imported
|
2013-01-04 10:35:29 +01:00
|
|
|
and used by the module defined in settings.OOB_FUNC_MODULE.
|
2013-01-03 09:18:49 +01:00
|
|
|
|
|
|
|
|
Import src.server.oobhandler and use the methods in OOBHANDLER.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2013-01-04 10:35:29 +01:00
|
|
|
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"
|
2013-08-30 20:53:38 +02:00
|
|
|
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
|
2013-01-04 10:35:29 +01:00
|
|
|
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from collections import defaultdict
|
|
|
|
|
from src.scripts.objects import ScriptDB
|
|
|
|
|
from src.scripts.script import Script
|
|
|
|
|
from src.server import caches
|
|
|
|
|
from src.server.caches import hashid
|
|
|
|
|
from src.utils import logger, create
|
|
|
|
|
|
2013-08-30 20:53:38 +02:00
|
|
|
class _OOBTracker(Script):
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
Active tracker script, handles subscriptions
|
|
|
|
|
"""
|
|
|
|
|
def at_script_creation(self):
|
|
|
|
|
"Called at script creation"
|
|
|
|
|
self.key = "oob_tracking_30" # default to 30 second interval
|
|
|
|
|
self.desc = "Active tracking of oob data"
|
|
|
|
|
self.interval = 30
|
|
|
|
|
self.persistent = False
|
|
|
|
|
self.start_delay = True
|
|
|
|
|
# holds dictionary of key:(function, (args,), {kwargs}) to call
|
|
|
|
|
self.db.subs = {}
|
|
|
|
|
|
|
|
|
|
def track(self, key, func, *args, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
Add sub to track. func(*args, **kwargs) will be called at self.interval.
|
|
|
|
|
key is a unique identifier for removing the tracking later.
|
|
|
|
|
"""
|
|
|
|
|
self.subs[key] = (func, args, kwargs)
|
|
|
|
|
|
|
|
|
|
def untrack(self, key):
|
|
|
|
|
"""
|
|
|
|
|
Clear a tracking. Return True if untracked successfully, None if
|
|
|
|
|
no previous track was found.
|
|
|
|
|
"""
|
|
|
|
|
if key in self.subs:
|
|
|
|
|
del self.subs[key]
|
|
|
|
|
if not self.subs:
|
|
|
|
|
# we have no more subs. Stop this script.
|
|
|
|
|
self.stop()
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def at_repeat(self):
|
|
|
|
|
"""
|
|
|
|
|
Loops through all subs, calling their given function
|
|
|
|
|
"""
|
|
|
|
|
for func, args, kwargs in self.subs:
|
|
|
|
|
try:
|
|
|
|
|
func(*args, **kwargs)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.log_trace()
|
|
|
|
|
|
|
|
|
|
class _OOBStore(Script):
|
|
|
|
|
"""
|
|
|
|
|
Store OOB data between restarts
|
|
|
|
|
"""
|
|
|
|
|
def at_script_creation(self):
|
|
|
|
|
"Called at script creation"
|
|
|
|
|
self.key = "oob_save_store"
|
|
|
|
|
self.desc = "Stores OOB data"
|
|
|
|
|
self.persistent = True
|
|
|
|
|
def save_oob_data(self, data):
|
|
|
|
|
self.db.store = data
|
|
|
|
|
def get_oob_data(self):
|
|
|
|
|
return self.db.store
|
|
|
|
|
|
|
|
|
|
class OOBhandler(object):
|
|
|
|
|
"""
|
|
|
|
|
Main Out-of-band handler
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self):
|
|
|
|
|
"initialization"
|
|
|
|
|
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
|
|
|
|
|
|
2013-01-07 15:47:41 +01:00
|
|
|
def track_passive(self, oobkey, tracker, tracked, entityname, callback=None, mode="db", *args, **kwargs):
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
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
|
2013-08-30 20:53:38 +02:00
|
|
|
entityname - field/property/attribute/ndb name to watch
|
2013-01-04 10:35:29 +01:00
|
|
|
function - function object to call when entity update. When entitye <key>
|
2013-01-03 09:18:49 +01:00
|
|
|
is updated, this function will be called with called
|
2013-01-07 15:47:41 +01:00
|
|
|
with function(obj, entityname, new_value, *args, **kwargs)
|
2013-01-03 09:18:49 +01:00
|
|
|
*args - additional, optional arguments to send to function
|
2013-01-04 10:35:29 +01:00
|
|
|
mode (keyword) - the type of entity to track. One of
|
2013-01-03 09:18:49 +01:00
|
|
|
"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
|
|
|
|
|
|
2013-01-07 15:47:41 +01:00
|
|
|
def default_callback(tracker, tracked, entityname, new_val, *args, **kwargs):
|
2013-01-04 10:35:29 +01:00
|
|
|
"Callback used if no function is supplied"
|
|
|
|
|
pass
|
|
|
|
|
|
2013-01-03 09:18:49 +01:00
|
|
|
thid = hashid(tracked)
|
|
|
|
|
if not thid:
|
|
|
|
|
return
|
2013-01-07 15:47:41 +01:00
|
|
|
oob_call = (function, oobkey, tracker, tracked, entityname, args, kwargs)
|
2013-01-03 09:18:49 +01:00
|
|
|
if thid not in self.track_passive_subs:
|
2013-01-04 10:35:29 +01:00
|
|
|
if mode in ("db", "ndb", "custom"):
|
2013-01-07 15:47:41 +01:00
|
|
|
caches.register_oob_update_hook(tracked, entityname, mode=mode)
|
2013-01-04 10:35:29 +01:00
|
|
|
elif mode == "property":
|
2013-01-03 09:18:49 +01:00
|
|
|
# track property/field. We must first determine which cache to use.
|
2013-01-07 15:47:41 +01:00
|
|
|
if hasattr(tracked, 'db_%s' % entityname.lstrip("db_")):
|
|
|
|
|
hid = caches.register_oob_update_hook(tracked, entityname, mode="field")
|
2013-01-03 09:18:49 +01:00
|
|
|
else:
|
2013-01-07 15:47:41 +01:00
|
|
|
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}
|
2013-01-03 09:18:49 +01:00
|
|
|
else:
|
2013-01-07 15:47:41 +01:00
|
|
|
self.track_passive_subs[hid][entityname][tracker] = oob_call
|
2013-01-03 09:18:49 +01:00
|
|
|
|
2013-01-07 15:47:41 +01:00
|
|
|
def untrack_passive(self, tracker, tracked, entityname, mode="db"):
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
Remove passive tracking from an object's entity.
|
2013-01-04 10:35:29 +01:00
|
|
|
mode - one of "property", "db", "ndb" or "custom"
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
try: tracked = tracked.dbobj
|
|
|
|
|
except AttributeError: pass
|
|
|
|
|
|
|
|
|
|
thid = hashid(tracked)
|
|
|
|
|
if not thid:
|
|
|
|
|
return
|
2013-01-07 15:47:41 +01:00
|
|
|
if len(self.track_passive_subs[thid][entityname]) == 1:
|
2013-01-04 10:35:29 +01:00
|
|
|
if mode in ("db", "ndb", "custom"):
|
2013-01-07 15:47:41 +01:00
|
|
|
caches.unregister_oob_update_hook(tracked, entityname, mode=mode)
|
2013-01-04 10:35:29 +01:00
|
|
|
elif mode == "property":
|
2013-01-07 15:47:41 +01:00
|
|
|
if hasattr(, 'db_%s' % entityname.lstrip("db_")):
|
|
|
|
|
caches.unregister_oob_update_hook(tracked, entityname, mode="field")
|
2013-01-03 09:18:49 +01:00
|
|
|
else:
|
2013-01-07 15:47:41 +01:00
|
|
|
caches.unregister_oob_update_hook(tracked, entityname, mode="property")
|
2013-01-03 09:18:49 +01:00
|
|
|
|
2013-01-07 15:47:41 +01:00
|
|
|
try: del self.track_passive_subs[thid][entityname][tracker]
|
2013-01-03 09:18:49 +01:00
|
|
|
except (KeyError, TypeError): pass
|
|
|
|
|
|
2013-01-07 15:47:41 +01:00
|
|
|
def update(self, hid, entityname, new_val):
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
2013-01-04 10:35:29 +01:00
|
|
|
This is called by the caches when the object when its
|
|
|
|
|
property/field/etc is updated, to inform the oob handler and
|
|
|
|
|
all subscribing to this particular entity has been updated
|
|
|
|
|
with new_val.
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
# tell all tracking objects of the update
|
2013-01-07 15:47:41 +01:00
|
|
|
for tracker, oob in self.track_passive_subs[hid][entityname].items():
|
2013-01-03 09:18:49 +01:00
|
|
|
try:
|
2013-01-07 15:47:41 +01:00
|
|
|
# function(oobkey, tracker, tracked, entityname, new_value, *args, **kwargs)
|
2013-01-03 09:18:49 +01:00
|
|
|
oob[0](tracker, oob[1], oob[2], new_val, *oob[3], **oob[4])
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.log_trace()
|
|
|
|
|
|
|
|
|
|
# Track (active/proactive tracking)
|
|
|
|
|
|
|
|
|
|
# creating and storing tracker scripts
|
2013-01-07 15:47:41 +01:00
|
|
|
def track_active(self, oobkey, func, interval=30, *args, **kwargs):
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
Create a tracking, re-use script with same interval if available,
|
|
|
|
|
otherwise create a new one.
|
|
|
|
|
|
|
|
|
|
args:
|
2013-01-07 15:47:41 +01:00
|
|
|
oobkey - interval-unique identifier needed for removing tracking later
|
2013-01-03 09:18:49 +01:00
|
|
|
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:
|
|
|
|
|
# tracker with given interval found. Add to its subs
|
2013-01-07 15:47:41 +01:00
|
|
|
self.track_active_subs[interval].track(oobkey, func, *args, **kwargs)
|
2013-01-03 09:18:49 +01:00
|
|
|
else:
|
|
|
|
|
# create new tracker with given interval
|
2013-01-07 15:47:41 +01:00
|
|
|
new_tracker = create.create_script(_OOBTracker, oobkey="oob_tracking_%i" % interval, interval=interval)
|
|
|
|
|
new_tracker.track(oobkey, func, *args, **kwargs)
|
2013-01-03 09:18:49 +01:00
|
|
|
self.track_active_subs[interval] = new_tracker
|
|
|
|
|
|
2013-01-07 15:47:41 +01:00
|
|
|
def untrack_active(self, oobkey, interval):
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
2013-01-07 15:47:41 +01:00
|
|
|
Remove tracking for a given interval and oobkey
|
2013-01-03 09:18:49 +01:00
|
|
|
"""
|
|
|
|
|
tracker = self.track_active_subs.get(interval)
|
|
|
|
|
if tracker:
|
2013-01-07 15:47:41 +01:00
|
|
|
tracker.untrack(oobkey)
|
2013-01-03 09:18:49 +01:00
|
|
|
|
|
|
|
|
# handler object
|
|
|
|
|
OOBHANDLER = OOBhandler()
|