diff --git a/src/cache.py b/src/cache.py new file mode 100644 index 0000000000..4d297464a6 --- /dev/null +++ b/src/cache.py @@ -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() + diff --git a/src/cmdhandler.py b/src/cmdhandler.py index f60da9decc..92da51a967 100755 --- a/src/cmdhandler.py +++ b/src/cmdhandler.py @@ -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): diff --git a/src/commands/batchprocess.py b/src/commands/batchprocess.py index 80dc66b60f..d0c680fc66 100644 --- a/src/commands/batchprocess.py +++ b/src/commands/batchprocess.py @@ -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 diff --git a/src/commands/objmanip.py b/src/commands/objmanip.py index f9782addea..a92cd5bcb1 100644 --- a/src/commands/objmanip.py +++ b/src/commands/objmanip.py @@ -44,14 +44,15 @@ def cmd_teleport(command): # a direct teleport, @tel . 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 = [] diff --git a/src/commands/privileged.py b/src/commands/privileged.py index 6cc522d3f3..b247fd4e3d 100644 --- a/src/commands/privileged.py +++ b/src/commands/privileged.py @@ -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/") 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.") diff --git a/src/objects/managers/object.py b/src/objects/managers/object.py index d98c57dcb6..032441d8a0 100644 --- a/src/objects/managers/object.py +++ b/src/objects/managers/object.py @@ -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) diff --git a/src/objects/models.py b/src/objects/models.py index 66cab27c08..9bd5c101cd 100755 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -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 diff --git a/src/scheduler.py b/src/scheduler.py index 42fb2553bf..a6632b52ff 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -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] diff --git a/src/session.py b/src/session.py index 1c40d41008..65bed0aecc 100755 --- a/src/session.py +++ b/src/session.py @@ -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): """