mirror of
https://github.com/evennia/evennia.git
synced 2026-04-02 22:17:17 +02:00
* Implemented a non-persistent cache in src/cache.py. The cache is lost when restarting the server but it has the advantage of not hitting the database, and so is useful for implementing things that should be remembered over time but does not need to be persistently saved in the database at every point, like fast-updating combat systems, timers etc. Using the cache can substantially cut down on database access at the cost of memory comsumption. It is easiest accessed through the object model using normal dot notation. So to store a variable in volatile memory e.g. from your script parent, you can do things like self.scripted_obj.cache.myvariable = variable and be sure that later (unless there was a reboot) doing self.scripted_obj.cache.myvariable will return the value you stored there.
* OBS - doing e.g. self.scripted_obj.myvariable = variable was always iffy and since a few revisions back this will NOT work - this is because the objects are now consistently synced with the database (in the past this was not done consistently which caused strange behaviour). * Fixed some bugs in the multi-word command handler. It can handle multi-word exits as well now.
This commit is contained in:
parent
af19724bb2
commit
642932a403
9 changed files with 177 additions and 58 deletions
79
src/cache.py
Normal file
79
src/cache.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
The cache module implements a volatile storage
|
||||
object mechanism for Evennia.
|
||||
|
||||
Data stored using this module is stored in
|
||||
memory (so requires no database access). The
|
||||
drawback is that it will be lost upon a
|
||||
reboot. It is however @reload-safe unless
|
||||
explicitly flushed with @reload/cache (the cache
|
||||
is not flushed with @reload/all)
|
||||
|
||||
Access I/O of the cache is normally done through
|
||||
the object model, using e.g.
|
||||
|
||||
source_object.cache.variable = data
|
||||
or
|
||||
data = source_object.cache.variable
|
||||
"""
|
||||
|
||||
# global storage. This can be references directly, but most
|
||||
# transparently it's accessed through the object model.
|
||||
|
||||
CACHE = {}
|
||||
|
||||
class Cache(dict):
|
||||
"""
|
||||
This storage object is stored to act as a save target for
|
||||
volatile variables through use of object properties. It
|
||||
can also be used as a dict if desired. It lists the contents
|
||||
of itself and makes sure to return None of the sought attribute
|
||||
is not set on itself (so test = cache.var will set test to None
|
||||
if cache has no attribute var instead of raising a traceback).
|
||||
|
||||
Each Cache object is intended to store the volatile properties
|
||||
of one in-game database object or one user-defined application.
|
||||
"""
|
||||
def __str__(self):
|
||||
"""
|
||||
Printing the cache object shows all properties
|
||||
stored on it.
|
||||
"""
|
||||
return ", ".join(sorted(self.__dict__.keys()))
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Make sure to return None if the attribute is not set.
|
||||
(instead of the usual traceback)
|
||||
"""
|
||||
return self.__dict__.get(name, None)
|
||||
|
||||
|
||||
def get(cache_key):
|
||||
"""
|
||||
Retrieve a cache object from the storage. This is primarily
|
||||
used by the objects.models.Object.cache property.
|
||||
|
||||
cache_key - identifies the cache storage area (e.g. an object dbref)
|
||||
"""
|
||||
if cache_key not in CACHE:
|
||||
CACHE[cache_key] = Cache()
|
||||
return CACHE[cache_key]
|
||||
|
||||
def flush(cache_key=None):
|
||||
"""
|
||||
Clears a particular cache_key from memory. If
|
||||
no key is given, entire cache is flushed.
|
||||
"""
|
||||
global CACHE
|
||||
if cache_key == None:
|
||||
CACHE = {}
|
||||
elif cache_key in CACHE:
|
||||
del CACHE[cache_key]
|
||||
|
||||
def show():
|
||||
"""
|
||||
Show objects stored in cache
|
||||
"""
|
||||
return CACHE.keys()
|
||||
|
||||
|
|
@ -89,8 +89,9 @@ class Command(object):
|
|||
|
||||
# add a space after the raw input; this cause split() to always
|
||||
# create a list with at least two entries.
|
||||
raw = "%s " % self.raw_input
|
||||
cmd_words = raw.split()
|
||||
raw = "%s " % self.raw_input
|
||||
cmd_words = raw.split(' ')
|
||||
|
||||
try:
|
||||
if '/' in cmd_words[0]:
|
||||
# if we have switches we directly go for the first command form.
|
||||
|
|
@ -112,8 +113,9 @@ class Command(object):
|
|||
# as tuples (commandname, args). They are stored with
|
||||
# the longest possible name first.
|
||||
try:
|
||||
command_alternatives.append( (" ".join(cmd_words[:spacecount+1]),
|
||||
" ".join(cmd_words[spacecount+1:])) )
|
||||
command_alternatives.append( (" ".join([w.strip()
|
||||
for w in cmd_words[:spacecount+1]]).strip(),
|
||||
" ".join(cmd_words[spacecount+1:]).strip()) )
|
||||
except IndexError:
|
||||
continue
|
||||
if command_alternatives:
|
||||
|
|
@ -362,6 +364,8 @@ def command_table_lookup(command, command_table, eval_perms=True,
|
|||
"""
|
||||
cmdtuple = None
|
||||
if command.command_alternatives:
|
||||
#print "alternatives:",command.command_alternatives
|
||||
#print command_table.ctable
|
||||
# we have command alternatives (due to spaces in command definition)
|
||||
for cmd_alternative in command.command_alternatives:
|
||||
# the alternatives are ordered longest -> shortest.
|
||||
|
|
@ -375,7 +379,7 @@ def command_table_lookup(command, command_table, eval_perms=True,
|
|||
if not cmdtuple:
|
||||
# None of the alternatives match, go with the default one-word name
|
||||
cmdtuple = command_table.get_command_tuple(command.command_string)
|
||||
|
||||
|
||||
if cmdtuple:
|
||||
# if we get here we have found a command match in the table
|
||||
if test:
|
||||
|
|
@ -411,8 +415,9 @@ def match_neighbor_ctables(command,test=False):
|
|||
location = source_object.get_location()
|
||||
if location:
|
||||
# get all objects, including the current room
|
||||
neighbors = location.get_contents() + [location]
|
||||
neighbors = location.get_contents() + [location] + source_object.get_contents()
|
||||
for neighbor in neighbors:
|
||||
#print "neighbor:", neighbor
|
||||
if command_table_lookup(command,
|
||||
neighbor.scriptlink.command_table,
|
||||
test=test, neighbor=neighbor):
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ def parse_batchbuild_file(filename):
|
|||
else: #comment
|
||||
if curr_cmd:
|
||||
commands.append(curr_cmd.strip())
|
||||
curr_cmd = ""
|
||||
curr_cmd = ""
|
||||
if curr_cmd: commands.append(curr_cmd.strip())
|
||||
|
||||
#second round to clean up now merged line edges etc.
|
||||
|
|
@ -145,7 +145,6 @@ def parse_batchbuild_file(filename):
|
|||
|
||||
#remove eventual newline at the end of commands
|
||||
commands = [c.strip('\r\n') for c in commands]
|
||||
|
||||
return commands
|
||||
|
||||
def batch_process(source_object, commands):
|
||||
|
|
@ -202,7 +201,6 @@ def cmd_batchprocess(command):
|
|||
if not source_object.set_state(STATENAME):
|
||||
source_object.emit_to("You cannot use the interactive mode while you have the flag ADMIN_NOSTATE set.")
|
||||
return
|
||||
|
||||
CMDSTACKS[source_object] = commands
|
||||
STACKPTRS[source_object] = 0
|
||||
FILENAMES[source_object] = filename
|
||||
|
|
@ -263,10 +261,10 @@ def process_commands(source_object, steps=0):
|
|||
for cmd in cmds:
|
||||
#this so it is kept in case of traceback
|
||||
STACKPTRS[source_object] = ptr + 1
|
||||
show_curr(source_object)
|
||||
#show_curr(source_object)
|
||||
source_object.execute_cmd(cmd)
|
||||
else:
|
||||
show_curr(source_object)
|
||||
#show_curr(source_object)
|
||||
source_object.execute_cmd(commands[ptr])
|
||||
|
||||
def reload_stack(source_object):
|
||||
|
|
@ -298,7 +296,7 @@ def exit_state(source_object):
|
|||
# (to avoid accidental drop-outs by rooms clearing a player's state),
|
||||
# we have to clear the state directly here.
|
||||
source_object.state = None
|
||||
|
||||
|
||||
def cmd_state_ll(command):
|
||||
"""
|
||||
ll
|
||||
|
|
|
|||
|
|
@ -44,14 +44,15 @@ def cmd_teleport(command):
|
|||
# a direct teleport, @tel <destination>.
|
||||
if len(eq_args) > 1:
|
||||
# Equal sign teleport.
|
||||
victim = source_object.search_for_object(eq_args[0])
|
||||
victim = source_object.search_for_object(eq_args[0].strip())
|
||||
# Use search_for_object to handle duplicate/nonexistant results.
|
||||
if not victim:
|
||||
return
|
||||
if victim.is_room():
|
||||
source_object.emit_to("You can't teleport a room.")
|
||||
return
|
||||
destination = source_object.search_for_object_global(eq_args[1],exact_match=True,
|
||||
destination = source_object.search_for_object_global(eq_args[1].strip(),
|
||||
exact_match=True,
|
||||
limit_types=[defines_global.OTYPE_THING,
|
||||
defines_global.OTYPE_ROOM])
|
||||
if not destination:
|
||||
|
|
@ -63,7 +64,7 @@ def cmd_teleport(command):
|
|||
victim.move_to(destination, quiet=tel_quietly)
|
||||
else:
|
||||
# Direct teleport (no equal sign)
|
||||
target_obj = source_object.search_for_object_global(eq_args[0],exact_match=True,
|
||||
target_obj = source_object.search_for_object_global(eq_args[0].strip(),exact_match=True,
|
||||
limit_types=[defines_global.OTYPE_THING,
|
||||
defines_global.OTYPE_ROOM])
|
||||
# Use search_for_object to handle duplicate/nonexistant results.
|
||||
|
|
@ -997,7 +998,10 @@ def cmd_dig(command):
|
|||
arg_list = args.split("=",1)
|
||||
if len(arg_list) < 2:
|
||||
#just create a room, no exits
|
||||
room_name = arg_list[0].strip()
|
||||
try:
|
||||
room_name, room_parent = [s.strip() for s in arg_list[0].split(":",1)]
|
||||
except ValueError:
|
||||
room_name = arg_list[0].strip()
|
||||
else:
|
||||
#deal with args left of =
|
||||
larg = arg_list[0]
|
||||
|
|
@ -1596,7 +1600,10 @@ def cmd_examine(command):
|
|||
locks = target_obj.get_attribute_value("LOCKS")
|
||||
if locks and "%s" % locks:
|
||||
string += str("Locks: %s" % locks) + newl
|
||||
|
||||
state = target_obj.get_state()
|
||||
if state:
|
||||
string += str("State: %s" % state) + newl
|
||||
|
||||
# Contents container lists for sorting by type.
|
||||
con_players = []
|
||||
con_things = []
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from src.helpsys.models import HelpEntry
|
|||
from src.helpsys import helpsystem
|
||||
from src.config.models import CommandAlias
|
||||
from src.config import edit_aliases
|
||||
|
||||
from src import cache
|
||||
def cmd_reload(command):
|
||||
"""
|
||||
@reload - reload game subsystems
|
||||
|
|
@ -26,30 +26,41 @@ def cmd_reload(command):
|
|||
aliases - alias definitions
|
||||
commands - the command modules
|
||||
scripts, parents - the script parent modules
|
||||
all
|
||||
all - reload all of the above
|
||||
|
||||
Reloads all the identified subsystems.
|
||||
cache - flush the volatile cache (warning, this
|
||||
might cause unexpected results if your
|
||||
script parents use the cache a lot)
|
||||
reset - flush cache then reload all
|
||||
|
||||
Reloads all the identified subsystems. Flushing the cache is
|
||||
generally not needed outside the main game development.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
switches = command.command_switches
|
||||
if not switches or switches[0] not in ['all','aliases','alias',
|
||||
'commands','command',
|
||||
'scripts','parents']:
|
||||
'scripts','parents',
|
||||
'cache','reset']:
|
||||
source_object.emit_to("Usage: @reload/<aliases|scripts|commands|all>")
|
||||
return
|
||||
switch = switches[0]
|
||||
sname = source_object.get_name(show_dbref=False)
|
||||
|
||||
if switch in ["all","aliases","alias"]:
|
||||
#reload Aliases
|
||||
|
||||
if switch in ["reset", "cache"]:
|
||||
# Clear the volatile cache
|
||||
cache.flush()
|
||||
comsys.cemit_mudinfo("%s flushed the non-persistent cache." % sname)
|
||||
if switch in ["reset","all","aliases","alias"]:
|
||||
# Reload Aliases
|
||||
command.session.server.reload_aliases(source_object=source_object)
|
||||
comsys.cemit_mudinfo("%s reloaded Aliases." % sname)
|
||||
if switch in ["all","scripts","parents"]:
|
||||
#reload Script parents
|
||||
if switch in ["reset","all","scripts","parents"]:
|
||||
# Reload Script parents
|
||||
rebuild_cache()
|
||||
comsys.cemit_mudinfo("%s reloaded Script parents." % sname)
|
||||
if switch in ["all","commands","command"]:
|
||||
#reload command objects.
|
||||
if switch in ["reset","all","commands","command"]:
|
||||
# Reload command objects.
|
||||
comsys.cemit_mudinfo("%s is reloading Command modules ..." % sname)
|
||||
command.session.server.reload(source_object=command.source_object)
|
||||
comsys.cemit_mudinfo("... all Command modules were reloaded.")
|
||||
|
|
|
|||
|
|
@ -174,7 +174,9 @@ class ObjectManager(models.Manager):
|
|||
defines_global.OTYPE_GOING])
|
||||
|
||||
def local_object_script_parent_search(self, script_parent, location):
|
||||
o_query = self.filter(script_parent__exact=script_parent).filter(location__iexact=location)
|
||||
o_query = self.filter(script_parent__exact=script_parent)
|
||||
if o_query:
|
||||
o_query = o_query.filter(location__iexact=location)
|
||||
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
|
||||
defines_global.OTYPE_GOING])
|
||||
|
||||
|
|
@ -367,12 +369,13 @@ class ObjectManager(models.Manager):
|
|||
|
||||
# If the search string is one of the following, return immediately with
|
||||
# the appropriate result.
|
||||
|
||||
if searcher.get_location().dbref_match(ostring) or ostring == 'here':
|
||||
return [searcher.get_location()]
|
||||
elif ostring == 'me' and searcher:
|
||||
return [searcher]
|
||||
|
||||
if search_query[0] == "*":
|
||||
if search_query and search_query[0] == "*":
|
||||
# Player search- gotta search by name or alias
|
||||
search_target = search_query[1:]
|
||||
player_match = self.player_name_search(search_target)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from src import scripthandler
|
|||
from src import defines_global
|
||||
from src import session_mgr
|
||||
from src import logger
|
||||
from src import cache
|
||||
|
||||
# Import as the absolute path to avoid local variable clashes.
|
||||
import src.flags
|
||||
|
|
@ -887,6 +888,8 @@ class Object(models.Model):
|
|||
functions_general.log_errmsg("Object '%s(#%d)' has invalid location: #%s" % \
|
||||
(self.name,self.id,self.location_id))
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def get_scriptlink(self):
|
||||
"""
|
||||
|
|
@ -905,6 +908,20 @@ class Object(models.Model):
|
|||
return None
|
||||
# Set a property to make accessing the scriptlink more transparent.
|
||||
scriptlink = property(fget=get_scriptlink)
|
||||
|
||||
def get_cache(self):
|
||||
"""
|
||||
Returns an object's volatile cache (in-memory storage)
|
||||
"""
|
||||
return cache.get(self.dbref())
|
||||
|
||||
def del_cache(self):
|
||||
"""
|
||||
Cleans the object cache for this object
|
||||
"""
|
||||
cache.flush(self.dbref())
|
||||
|
||||
cache = property(fget=get_cache, fdel=del_cache)
|
||||
|
||||
def get_script_parent(self):
|
||||
"""
|
||||
|
|
@ -1121,7 +1138,7 @@ class Object(models.Model):
|
|||
"""
|
||||
Returns the player's current state.
|
||||
"""
|
||||
return self.state
|
||||
return self.cache.state
|
||||
|
||||
def set_state(self, state_name=None):
|
||||
"""
|
||||
|
|
@ -1136,7 +1153,7 @@ class Object(models.Model):
|
|||
"""
|
||||
if not self.is_player():
|
||||
return False
|
||||
|
||||
|
||||
if self.is_superuser():
|
||||
# we have to deal with superusers separately since
|
||||
# they would always appear to have the genperm.admin_nostate
|
||||
|
|
@ -1148,12 +1165,12 @@ class Object(models.Model):
|
|||
# for other users we request the permission as normal.
|
||||
nostate = self.has_perm("genperms.admin_nostate")
|
||||
|
||||
# we never enter other states if we are in the interactive batch processor.
|
||||
nostate = nostate or self.state == "_interactive batch processor"
|
||||
# we never enter other states if we are already in the interactive batch processor.
|
||||
nostate = nostate or self.get_state() == "_interactive batch processor"
|
||||
|
||||
if nostate:
|
||||
return False
|
||||
self.state = state_name
|
||||
self.cache.state = state_name
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ def add_event(event):
|
|||
"""
|
||||
|
||||
#don't add multiple instances of the same event, instead replace
|
||||
if event in schedule:
|
||||
if event in schedule:
|
||||
schedule[schedule.index(event)] = event
|
||||
return
|
||||
else:
|
||||
|
|
@ -36,4 +36,4 @@ def del_event(event):
|
|||
if event in schedule:
|
||||
i = schedule.index(event)
|
||||
schedule[i].stop_event_loop()
|
||||
del schedule[i]
|
||||
del schedule[i]
|
||||
|
|
|
|||
|
|
@ -47,11 +47,7 @@ class SessionProtocol(StatefulTelnetProtocol):
|
|||
self.address = self.getClientAddress()
|
||||
self.name = None
|
||||
self.uid = None
|
||||
# This is merely here to serve as a convenient reference. It is not
|
||||
# necessarily the most up to date version of the object, so it is NOT
|
||||
# safe to look at pobject's location or any other attributes from
|
||||
# the Object model.
|
||||
self.pobject = None
|
||||
#self.pobject = None
|
||||
self.logged_in = False
|
||||
# The time the user last issued a command.
|
||||
self.cmd_last = time.time()
|
||||
|
|
@ -96,7 +92,7 @@ class SessionProtocol(StatefulTelnetProtocol):
|
|||
"""
|
||||
Sends a command to this session's object for processing.
|
||||
"""
|
||||
self.pobject.execute_cmd(command_str, session=self)
|
||||
self.get_pobject().pobject.execute_cmd(command_str, session=self)
|
||||
|
||||
def count_command(self, silently=False):
|
||||
"""
|
||||
|
|
@ -132,17 +128,20 @@ class SessionProtocol(StatefulTelnetProtocol):
|
|||
self.logged_in = False
|
||||
session_mgr.remove_session(self)
|
||||
|
||||
def get_pobject(self):
|
||||
def get_pobject(self, log_err=True):
|
||||
"""
|
||||
Returns the object associated with a session.
|
||||
"""
|
||||
try:
|
||||
# Cache the result in the session object for quick retrieval.
|
||||
result = Object.objects.get(id=self.uid)
|
||||
self.pobject = result
|
||||
return result
|
||||
# Cache the result in the session object during
|
||||
# the login procedure.
|
||||
#result = Object.objects.get(id=self.uid)
|
||||
#self.pobject = result
|
||||
#return result
|
||||
return Object.objects.get(id=self.uid)
|
||||
except:
|
||||
logger.log_errmsg("No pobject match for session uid: %s" % self.uid)
|
||||
if log_err:
|
||||
logger.log_errmsg("No pobject match for session uid: %s" % self.uid)
|
||||
return None
|
||||
|
||||
def game_connect_screen(self):
|
||||
|
|
@ -170,13 +169,14 @@ class SessionProtocol(StatefulTelnetProtocol):
|
|||
self.name = user.username
|
||||
self.logged_in = True
|
||||
self.conn_time = time.time()
|
||||
# This will cache with the first call of this function.
|
||||
self.get_pobject()
|
||||
|
||||
pobject = self.get_pobject()
|
||||
|
||||
#session_mgr.disconnect_duplicate_session(self)
|
||||
|
||||
if first_login:
|
||||
self.pobject.scriptlink.at_first_login(self)
|
||||
self.pobject.scriptlink.at_pre_login(self)
|
||||
pobject.scriptlink.at_first_login(self)
|
||||
pobject.scriptlink.at_pre_login(self)
|
||||
|
||||
logger.log_infomsg("Logged in: %s" % self)
|
||||
self.cemit_info('Logged in: %s' % self)
|
||||
|
|
@ -186,12 +186,11 @@ class SessionProtocol(StatefulTelnetProtocol):
|
|||
user.save()
|
||||
|
||||
# In case the account and the object get out of sync, fix it.
|
||||
if self.pobject.name != user.username:
|
||||
self.pobject.set_name(user.username)
|
||||
self.pobject.save()
|
||||
|
||||
self.pobject.scriptlink.at_post_login(self)
|
||||
if pobject.name != user.username:
|
||||
pobject.set_name(user.username)
|
||||
pobject.save()
|
||||
|
||||
pobject.scriptlink.at_post_login(self)
|
||||
|
||||
def msg(self, message):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue