First, untested version of the OOBhandler mechanism.

This commit is contained in:
Griatch 2013-01-03 09:18:49 +01:00
parent 90a64a3780
commit b0b0fa7983
9 changed files with 454 additions and 106 deletions

View file

@ -396,6 +396,13 @@ class DoNothing(Script):
self.key = "sys_do_nothing"
self.desc = _("This is an empty placeholder script.")
class Store(Script):
"Simple storage script"
def at_script_creation(self):
"Setup the script"
self.key = "sys_storage"
self.desc = _("This is a generic storage container.")
class CheckSessions(Script):
"Check sessions regularly."
def at_script_creation(self):

View file

@ -5,7 +5,6 @@ Central caching module.
from sys import getsizeof
from collections import defaultdict
from weakref import WeakKeyDictionary
_GA = object.__getattribute__
_SA = object.__setattr__
@ -16,6 +15,14 @@ _ATTR_CACHE = defaultdict(dict)
_FIELD_CACHE = defaultdict(dict)
_PROP_CACHE = defaultdict(dict)
# OOB hooks
_OOB_FIELD_UPDATE_HOOKS = defaultdict(dict)
_OOB_PROP_UPDATE_HOOKS = defaultdict(dict)
_OOB_ATTR_UPDATE_HOOKS = defaultdict(dict)
_OOB_NDB_UPDATE_HOOKS = defaultdict(dict)
_OOB_CUSTOM_UPDATE_HOOKS = defaultdict(dict)
_OOB_HANDLER = None # set by oob handler when it initializes
def get_cache_sizes():
"""
@ -46,16 +53,71 @@ def hashid(obj):
try:
hid = _GA(obj, "_hashid")
except AttributeError:
date, idnum = _GA(obj, "db_date_created"), _GA(obj, "id")
if not idnum or not date:
# this will happen if setting properties on an object
# which is not yet saved
return None
try:
date, idnum = _GA(obj, "db_date_created"), _GA(obj, "id")
if not idnum or not date:
# this will happen if setting properties on an object
# which is not yet saved
return None
except AttributeError:
# this happens if hashing something like ndb. We have to
# rely on memory adressing in this case.
date, idnum = "Nondb", id(obj)
# build the hashid
hid = "%s-%s-#%s" % (_GA(obj, "__class__"), date, idnum)
_SA(obj, "_hashid", hid)
return hid
# oob helper functions
def register_oob_update_hook(obj,name, entity="field"):
"""
Register hook function to be called when field/property/db/ndb is updated.
Given function will be called with function(obj, entityname, newvalue, *args, **kwargs)
entity - one of "field", "property", "db", "ndb" or "custom"
"""
hid = hashid(obj)
if hid:
if entity == "field":
global _OOB_FIELD_UPDATE_HOOKS
_OOB_FIELD_UPDATE_HOOKS[hid][name] = True
elif entity == "property":
global _OOB_PROP_UPDATE_HOOKS
_OOB_PROP_UPDATE_HOOKS[hid][name] = True
elif entity == "db":
global _OOB_ATTR_UPDATE_HOOKS
_OOB_ATTR_UPDATE_HOOKS[hid][name] = True
elif entity == "ndb":
global _OOB_NDB_UPDATE_HOOKS
_OOB_NDB_UPDATE_HOOKS[hid][name] = True
elif entity == "custom":
global _OOB_CUSTOM_UPDATE_HOOKS
_OOB_CUSTOM_UPDATE_HOOKS[hid][name] = True
else:
return None
return hid
def unregister_oob_update_hook(obj, name, entity="property"):
"""
Un-register a report hook
"""
hid = hashid(obj)
if hid:
global _OOB_FIELD_UPDATE_HOOKS,_OOB_PROP_UPDATE_HOOKS, _OOB_ATTR_UPDATE_HOOKS
global _OOB_CUSTOM_UPDATE_HOOKS, _OOB_NDB_UPDATE_HOOKS
if entity == "field" and name in _OOB_FIELD_UPDATE_HOOKS:
del _OOB_FIELD_UPDATE_HOOKS[hid][name]
elif entity == "property" and name in _OOB_PROP_UPDATE_HOOKS:
del _OOB_PROP_UPDATE_HOOKS[hid][name]
elif entity == "db" and name in _OOB_ATTR_UPDATE_HOOKS:
del _OOB_ATTR_UPDATE_HOOKS[hid][name]
elif entity == "ndb" and name in _OOB_NDB_UPDATE_HOOKS:
del _OOB_NDB_UPDATE_HOOKS[hid][name]
elif entity == "custom" and name in _OOB_CUSTOM_UPDATE_HOOKS:
del _OOB_CUSTOM_UPDATE_HOOKS[hid][name]
else:
return None
return hid
# on-object database field cache
def get_field_cache(obj, name):
"On-model Cache handler."
@ -78,6 +140,9 @@ def set_field_cache(obj, name, val):
if hid:
global _FIELD_CACHE
_FIELD_CACHE[hid][name] = val
# oob hook functionality
if _OOB_FIELD_UPDATE_HOOKS[hid].get(name):
_OOB_HANDLER.update(hid, name, val)
def del_field_cache(obj, name):
"On-model cache deleter"
@ -110,7 +175,7 @@ def get_prop_cache(obj, name, default=None):
hid = hashid(obj)
if hid:
try:
return _PROP_CACHE[hid][name]
val = _PROP_CACHE[hid][name]
except KeyError:
return default
_PROP_CACHE[hid][name] = val
@ -123,6 +188,11 @@ def set_prop_cache(obj, name, val):
if hid:
global _PROP_CACHE
_PROP_CACHE[hid][name] = val
# oob hook functionality
oob_hook = _OOB_PROP_UPDATE_HOOKS[hid].get(name)
if oob_hook:
oob_hook[0](obj.typeclass, name, val, *oob_hook[1], **oob_hook[2])
def del_prop_cache(obj, name):
"On-model cache deleter"
@ -130,7 +200,7 @@ def del_prop_cache(obj, name):
del _PROP_CACHE[hashid(obj)][name]
except KeyError:
pass
def flush_field_cache(obj=None):
def flush_prop_cache(obj=None):
"On-model cache resetter"
hid = hashid(obj)
global _PROP_CACHE
@ -152,10 +222,14 @@ def set_attr_cache(obj, attrname, attrobj):
"""
Cache an attribute object
"""
global _ATTR_CACHE
hid = hashid(obj)
if hid:
global _ATTR_CACHE
_ATTR_CACHE[hid][attrname] = attrobj
# oob hook functionality
oob_hook = _OOB_ATTR_UPDATE_HOOKS[hid].get(attrname)
if oob_hook:
oob_hook[0](obj.typeclass, attrname, attrobj.value, *oob_hook[1], **oob_hook[2])
def del_attr_cache(obj, attrname):
"""
@ -177,3 +251,26 @@ def flush_attr_cache(obj=None):
else:
# clean cache completely
_ATTR_CACHE = defaultdict(dict)
def call_ndb_hooks(obj, attrname, value):
"""
No caching is done of ndb here, but
we use this as a way to call OOB hooks.
"""
hid = hashid(obj)
if hid:
oob_hook = _OOB_NDB_UPDATE_HOOKS[hid].get(attrname)
if oob_hook:
oob_hook[0](obj.typeclass, attrname, value, *oob_hook[1], **oob_hook[2])
def call_custom_hooks(obj, attrname, value):
"""
Custom handler for developers adding their own oob hooks, e.g. to
custom typeclass properties.
"""
hid = hashid(obj)
if hid:
oob_hook = _OOB_CUSTOM_UPDATE_HOOKS[hid].get(attrname)
if oob_hook:
oob_hook[0](obj.typeclass, attrname, value, *oob_hook[1], **oob_hook[2])

View file

@ -107,7 +107,7 @@ class ServerConfig(SharedMemoryModel):
def __unicode__(self):
return "%s : %s" % (self.key, self.value)
def store(key, value):
def store(self, key, value):
"""
Wrap the storage (handles pickling)
"""

View file

@ -15,13 +15,6 @@ from django.conf import settings
from src.utils.utils import make_iter, mod_import
from src.utils import logger
# custom functions
OOC_MODULE = mod_import(settings.OOB_FUNC_MODULE)
OOC_FUNCS = dict((key.upper(), var) for key, var in OOC_MODULE if not key.startswith('__') and callable(var))
# MSDP commands supported by Evennia
MSDP_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT")
# MSDP-relevant telnet cmd/opt-codes
MSDP = chr(69)
MSDP_VAR = chr(1)
@ -36,6 +29,92 @@ regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY
regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # return 2-tuple (may be nested)
regex_varval = re.compile(r"%s(.*?)%s(.*?)" % (MSDP_VAR, MSDP_VAL)) # return 2-tuple
# MSDP default definition commands supported by Evennia (can be supplemented with custom commands as well)
MSDP_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT")
# fallbacks if no custom OOB module is available
MSDP_COMMANDS_CUSTOM = {}
# MSDP_REPORTABLE is a standard suggestions for making it easy to create generic guis.
# this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It
# is up to these commands to return data on proper form. This is overloaded if
# OOB_REPORTABLE is defined in the custom OOB module below.
MSDP_REPORTABLE = {
# General
"CHARACTER_NAME": "get_character_name",
"SERVER_ID": "get_server_id",
"SERVER_TIME": "get_server_time",
# Character
"AFFECTS": "char_affects",
"ALIGNMENT": "char_alignment",
"EXPERIENCE": "char_experience",
"EXPERIENCE_MAX": "char_experience_max",
"EXPERIENCE_TNL": "char_experience_tnl",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"RACE": "char_race",
"CLASS": "char_class",
"MANA": "char_mana",
"MANA_MAX": "char_mana_max",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"MOVEMENT": "char_movement",
"MOVEMENT_MAX": "char_movement_max",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"AC": "char_ac",
"STR": "char_str",
"INT": "char_int",
"WIS": "char_wis",
"DEX": "char_dex",
"CON": "char_con",
# Combat
"OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name",
# World
"AREA_NAME": "area_name",
"ROOM_EXITS": "area_room_exits",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"WORLD_TIME": "world_time",
# Configurable variables
"CLIENT_ID": "client_id",
"CLIENT_VERSION": "client_version",
"PLUGIN_ID": "plugin_id",
"ANSI_COLORS": "ansi_colours",
"XTERM_256_COLORS": "xterm_256_colors",
"UTF_8": "utf_8",
"SOUND": "sound",
"MXP": "mxp",
# GUI variables
"BUTTON_1": "button1",
"BUTTON_2": "button2",
"BUTTON_3": "button3",
"BUTTON_4": "button4",
"BUTTON_5": "button5",
"GAUGE_1": "gauge1",
"GAUGE_2": "gauge2",
"GAUGE_3": "gauge3",
"GAUGE_4": "gauge4",
"GAUGE_5": "gauge5"}
MSDP_SENDABLE = MSDP_REPORTABLE
# try to load custom OOB module
OOB_MODULE = mod_import(settings.OOB_FUNC_MODULE)
if OOB_MODULE:
# loading customizations from OOB_FUNC_MODULE if available
try: MSDP_REPORTABLE = OOB_MODULE.OOB_REPORTABLE # replaces the default MSDP definitions
except AttributeError: pass
try: MSDP_SENDABLE = OOB_MODULE.OOB_SENDABLE
except AttributeError: MSDP_SENDABLE = MSDP_REPORTABLE
try: MSDP_COMMANDS_CUSTOM = OOB_MODULE.OOB_COMMANDS
except: pass
# Msdp object handler
class Msdp(object):
"""
Implements the MSDP protocol.
@ -51,6 +130,7 @@ class Msdp(object):
self.protocol.protocol_FLAGS['MSDP'] = False
self.protocol.negotiationMap['MSDP'] = self.parse_msdp
self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp)
self.msdp_reported = {}
def no_msdp(self, option):
"No msdp supported or wanted"
@ -110,7 +190,7 @@ class Msdp(object):
Handle a client's requested negotiation, converting
it into a function mapping - either one of the MSDP
default functions (LIST, SEND etc) or a custom one
in OOC_FUNCS dictionary. command names are case-insensitive.
in OOB_FUNCS dictionary. command names are case-insensitive.
varname, var --> mapped to function varname(var)
arrayname, array --> mapped to function arrayname(*array)
@ -133,6 +213,7 @@ class Msdp(object):
variables = dict((key.upper(), val) for key, val in regex_varval(regex_array.sub("", regex_table.sub("", data))))
ret = ""
# default MSDP functions
if "LIST" in variables:
ret += self.func_to_msdp("LIST", self.msdp_cmd_list(variables["LIST"]))
@ -150,7 +231,7 @@ class Msdp(object):
ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*arrays["RESET"]))
del arrays["RESET"]
if "SEND" in variables:
ret += self.func_to_msdp("SEND", self.msdp_cmd_send((*variables["SEND"],)))
ret += self.func_to_msdp("SEND", self.msdp_cmd_send(*(variables["SEND"],)))
del variables["SEND"]
if "SEND" in arrays:
ret += self.func_to_msdp("SEND",self.msdp_cmd_send(*arrays["SEND"]))
@ -159,17 +240,17 @@ class Msdp(object):
# if there are anything left we look for a custom function
for varname, var in variables.items():
# a simple function + argument
ooc_func = OOC_FUNCS.get(varname.upper())
ooc_func = MSDP_COMMANDS_CUSTOM.get(varname.upper())
if ooc_func:
ret += self.func_to_msdp(varname, ooc_func(var))
for arrayname, array in arrays.items():
# we assume the array are multiple arguments to the function
ooc_func = OOC_FUNCS.get(arrayname.upper())
ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper())
if ooc_func:
ret += self.func_to_msdp(arrayname, ooc_func(*array))
for tablename, table in tables.items():
# we assume tables are keyword arguments to the function
ooc_func = OOC_FUNCS.get(arrayname.upper())
ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper())
if ooc_func:
ret += self.func_to_msdp(tablename, ooc_func(**table))
return ret
@ -184,28 +265,31 @@ class Msdp(object):
The List command allows for retrieving various info about the server/client
"""
if arg == 'COMMANDS':
return self.func_to_msdp(arg, MSDP_COMMANDS))
return self.func_to_msdp(arg, MSDP_COMMANDS)
elif arg == 'LISTS':
return self.func_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES",
"REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
elif arg == 'CONFIGURABLE_VARIABLES':
return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
elif arg == 'REPORTABLE_VARIABLES':
return self.func_to_msdp(arg, self.MSDP_REPORTABLE.keys())
return self.func_to_msdp(arg, MSDP_REPORTABLE.keys())
elif arg == 'REPORTED_VARIABLES':
return self.func_to_msdp(arg, self.MSDP_REPORTED.keys())
# the dynamically set items to report
return self.func_to_msdp(arg, self.msdp_reported.keys())
elif arg == 'SENDABLE_VARIABLES':
return self.func_to_msdp(arg, self.MSDP_SEND.keys())
return self.func_to_msdp(arg, MSDP_SENDABLE.keys())
else:
return self.func_to_msdp("LIST", arg)
# default msdp commands
def msdp_cmd_report(self, *arg):
"""
The report command instructs the server to start reporting a
reportable variable to the client.
"""
try:
self.MSDP_REPORTABLE[arg](report=True)
MSDP_REPORTABLE[arg](report=True)
except Exception:
logger.log_trace()
@ -214,7 +298,7 @@ class Msdp(object):
Unreport a previously reported variable
"""
try:
self.MSDP_REPORTABLE[arg](report=False)
MSDP_REPORTABLE[arg](report=False)
except Exception:
self.logger.log_trace()
@ -223,7 +307,7 @@ class Msdp(object):
The reset command resets a variable to its initial state.
"""
try:
self.MSDP_REPORTABLE[arg](reset=True)
MSDP_REPORTABLE[arg](reset=True)
except Exception:
logger.log_trace()
@ -237,79 +321,9 @@ class Msdp(object):
ret = []
for var in make_iter(arg):
try:
ret.append(self.MSDP_REPORTABLE[arg](send=True))
ret.append(MSDP_REPORTABLE[arg](send=True))
except Exception:
logger.log_trace()
return ret
# MSDP_MAP is a standard suggestions for making it easy to create generic guis.
# this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It
# is up to these commands to return data on proper form.
MSDP_REPORTABLE = {
# General
"CHARACTER_NAME": "get_character_name",
"SERVER_ID": "get_server_id",
"SERVER_TIME": "get_server_time",
# Character
"AFFECTS": "char_affects",
"ALIGNMENT": "char_alignment",
"EXPERIENCE": "char_experience",
"EXPERIENCE_MAX": "char_experience_max",
"EXPERIENCE_TNL": "char_experience_tnl",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"RACE": "char_race",
"CLASS": "char_class",
"MANA": "char_mana",
"MANA_MAX": "char_mana_max",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"MOVEMENT": "char_movement",
"MOVEMENT_MAX": "char_movement_max",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"AC": "char_ac",
"STR": "char_str",
"INT": "char_int",
"WIS": "char_wis",
"DEX": "char_dex",
"CON": "char_con",
# Combat
"OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name",
# World
"AREA_NAME": "area_name",
"ROOM_EXITS": "area_room_exits",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"WORLD_TIME": "world_time",
# Configurable variables
"CLIENT_ID": "client_id",
"CLIENT_VERSION": "client_version",
"PLUGIN_ID": "plugin_id",
"ANSI_COLORS": "ansi_colours",
"XTERM_256_COLORS": "xterm_256_colors",
"UTF_8": "utf_8",
"SOUND": "sound",
"MXP": "mxp",
# GUI variables
"BUTTON_1": "button1",
"BUTTON_2": "button2",
"BUTTON_3": "button3",
"BUTTON_4": "button4",
"BUTTON_5": "button5",
"GAUGE_1": "gauge1",
"GAUGE_2": "gauge2",
"GAUGE_3": "gauge3",
"GAUGE_4": "gauge4",
"GAUGE_5": "gauge5"}

224
src/server/oobhandler.py Normal file
View file

@ -0,0 +1,224 @@
"""
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
and used by the module defined in settings.OOB_MODULE.
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.
"""
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
class _OOBTracker(Script):
"""
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
def track_passive(self, tracker, tracked, name, function, entity="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
name - field/property/attribute/ndb nam to watch
function - function object to call when entity update. When entitye <name>
is updated, this function will be called with called
with function(obj, name, new_value, *args, **kwargs)
*args - additional, optional arguments to send to function
entity (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
thid = hashid(tracked)
if not thid:
return
oob_call = (function, tracked, name, args, kwargs)
if thid not in self.track_passive_subs:
if entity in ("db", "ndb", "custom"):
caches.register_oob_update_hook(tracked, name, entity=entity)
elif entity == "property":
# track property/field. We must first determine which cache to use.
if hasattr(tracked, 'db_%s' % name.lstrip("db_")):
hid = caches.register_oob_update_hook(tracked, name, entity="field")
else:
hid = caches.register_oob_update_hook(tracked, name, entity="property")
if not self.track_pass_subs[hid][name]:
self.track_pass_subs[hid][name] = {tracker:oob_call}
else:
self.track_passive_subs[hid][name][tracker] = oob_call
def untrack_passive(self, tracker, tracked, name, entity="db"):
"""
Remove passive tracking from an object's entity.
entity - one of "property", "db", "ndb" or "custom"
"""
try: tracked = tracked.dbobj
except AttributeError: pass
thid = hashid(tracked)
if not thid:
return
if len(self.track_passive_subs[thid][name]) == 1:
if entity in ("db", "ndb", "custom"):
caches.unregister_oob_update_hook(tracked, name, entity=entity)
elif entity == "property":
if hasattr(tracked, 'db_%s' % name.lstrip("db_")):
caches.unregister_oob_update_hook(tracked, name, entity="field")
else:
caches.unregister_oob_update_hook(tracked, name, entity="property")
try: del self.track_passive_subs[thid][name][tracker]
except (KeyError, TypeError): pass
def update(self, hid, name, new_val):
"""
This is called by the caches when the tracked 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.
"""
# tell all tracking objects of the update
for tracker, oob in self.track_passive_subs[hid][name].items():
try:
# function(tracker, tracked, key, new_value, *args, **kwargs)
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
def track_active(self, key, func, interval=30, *args, **kwargs):
"""
Create a tracking, re-use script with same interval if available,
otherwise create a new one.
args:
key - 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:
# tracker with given interval found. Add to its subs
self.track_active_subs[interval].track(key, func, *args, **kwargs)
else:
# create new tracker with given interval
new_tracker = create.create_script(_OOBTracker, key="oob_tracking_%i" % interval, interval=interval)
new_tracker.track(key, func, *args, **kwargs)
self.track_active_subs[interval] = new_tracker
def untrack_active(self, key, interval):
"""
Remove tracking for a given interval and key
"""
tracker = self.track_active_subs.get(interval)
if tracker:
tracker.untrack(key)
# handler object
OOBHANDLER = OOBhandler()

View file

@ -142,7 +142,7 @@ class Evennia(object):
if len(mismatches): # can't use any() since mismatches may be [0] which reads as False for any()
# we have a changed default. Import relevant objects and run the update
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
#from src.players.models import PlayerDB
for i, prev, curr in ((i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches):
# update the database
print " one or more default cmdset/typeclass settings changed. Updating defaults stored in database ..."
@ -188,7 +188,7 @@ class Evennia(object):
Called every server start
"""
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
#from src.players.models import PlayerDB
#update eventual changed defaults
self.update_defaults()
@ -264,7 +264,7 @@ class Evennia(object):
# call shutdown hooks on all cached objects
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
#from src.players.models import PlayerDB
from src.server.models import ServerConfig
if mode == 'reload':

View file

@ -228,6 +228,8 @@ class ServerSession(Session):
example:
data = {"get_hp": ([], {}),
"update_counter", (["counter1"], {"now":True}) }
All functions will be called with a back-reference to this session as first argument.
"""
outdata = {}
@ -243,7 +245,7 @@ class ServerSession(Session):
func = OOB_FUNC_MODULE.__dict__.get(funcname, None)
if func:
try:
outdata[funcname] = func(entity, *argtuple[0], **argtuple[1])
outdata[funcname] = func(self, entity, *argtuple[0], **argtuple[1])
except Exception:
logger.log_trace()
else:

View file

@ -35,7 +35,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.mccp = Mccp(self)
# negotiate ttype (client info)
self.ttype = ttype.Ttype(self)
# negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self)

View file

@ -42,6 +42,7 @@ from src.utils.idmapper.models import SharedMemoryModel
from src.server.caches import get_field_cache, set_field_cache, del_field_cache
from src.server.caches import get_attr_cache, set_attr_cache, del_attr_cache
from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache
from src.server.caches import call_ndb_hooks
from src.server.models import ServerConfig
from src.typeclasses import managers
from src.locks.lockhandler import LockHandler
@ -1584,7 +1585,7 @@ class TypedObject(SharedMemoryModel):
return None
else:
# act as a setter
_SA(self.db, attribute_name, value)
_SA(self.ndb, attribute_name, value)
#@property
def __ndb_get(self):
@ -1609,6 +1610,10 @@ class TypedObject(SharedMemoryModel):
return _GA(self, key)
except AttributeError:
return None
def __setattr__(self, key, value):
# hook the oob handler here
call_ndb_hooks(self, key, value)
_SA(self, key, value)
self._ndb_holder = NdbHolder()
return self._ndb_holder
#@ndb.setter