def __init__(self, mud_service, db_mode='production'): """ :param MudService mud_service: The MudService class running the game. :keyword str mode: Either 'test' or 'production'. """ self._mud_service = mud_service # This is used to sub-classes of BaseObject. self.parent_loader = ParentLoader() # Keys are object IDs, values are the parent instances (children of # src.game.parents.base_objects.base.BaseObject) self._objects = {} # DB abstraction layer. self.db_manager = DBManager(mud_service, self.parent_loader, db_mode)
class ObjectStore(object): """ Serves as an in-memory object store for all "physical" entities in the game. An "object" can be stuff like a room or a thing. Objects are persisted to a DB via the :py:attr:`db_manager` attribute, which is a reference to a :py:class:`src.server.objects.db_io.DBManager` instance. .. note:: This class should know nothing about the DB that backs it. Make sure to keep any DB-related things out of here. """ def __init__(self, mud_service, db_mode='production'): """ :param MudService mud_service: The MudService class running the game. :keyword str mode: Either 'test' or 'production'. """ self._mud_service = mud_service # This is used to sub-classes of BaseObject. self.parent_loader = ParentLoader() # Keys are object IDs, values are the parent instances (children of # src.game.parents.base_objects.base.BaseObject) self._objects = {} # DB abstraction layer. self.db_manager = DBManager(mud_service, self.parent_loader, db_mode) @inlineCallbacks def prep_and_load(self): """ This runs early in server startup. Calls on the DBManager (self.db_manager) to prep the DB and load all objects. """ is_first_run = yield self.db_manager.prepare_and_load() if is_first_run: # If this is the first time the server has been started, we'll # need to create the starter room. parent_path = 'src.game.parents.base_objects.room.RoomObject' yield self.create_object(parent_path, name='And so it begins...') def loader_func(obj): """ This function runs on each object instantiated from the DB at start time. :param BaseObject obj: The object to load into the store. """ self._objects[obj.id] = obj yield self.db_manager.load_objects_into_store(loader_func) @inlineCallbacks def create_object(self, parent_path, name, **kwargs): """ Creates and saves a new object of the specified parent. :param str parent_path: The full Python path + class name for a parent. for example, src.game.parents.base_objects.room.RoomObject. :param str name: The name of the object. :param dict kwargs: Additional attributes to set on the object. :rtype: BaseObject :returns: The newly created/instantiated/saved object. """ #noinspection PyPep8Naming NewObject = self.parent_loader.load_parent(parent_path) obj = NewObject( self._mud_service, id=None, name=name, parent=parent_path, **kwargs ) obj = yield self.save_object(obj) returnValue(obj) @inlineCallbacks def save_object(self, obj): """ Saves an object to the DB. :param BaseObject obj: The object to save to the DB. """ saved_obj = yield self.db_manager.save_object(obj) self._objects[saved_obj.id] = saved_obj returnValue(saved_obj) @inlineCallbacks def destroy_object(self, obj): """ Destroys an object by yanking it from :py:attr:`_objects` and the DB. :param BaseObject obj: The object to destroy. """ yield self.db_manager.destroy_object(obj) # Clear the object out of the store, mark it for GC. del self._objects[obj.id] del obj @inlineCallbacks def reload_object(self, obj): """ Re-loads the object from the DB. :param BaseObject obj: The object to re-load from the DB. :rtype: BaseObject :returns: The newly re-loaded object. """ reloaded_obj = yield self.db_manager.reload_object(obj) self._objects[reloaded_obj.id] = reloaded_obj returnValue(reloaded_obj) def get_object(self, obj_id): """ Given an object ID, return the object's instance. :param int obj_id: The ID of the object to return. :returns: The requested object, which will be a :class:`BaseObject` sub-class of some sort. :raises: :py:exc:`src.server.objects.exceptions.NoSuchObject` if no object with the requested ID exists. """ assert isinstance(obj_id, int), \ "get_object had a non-int passed: %s %s" % (obj_id, type(obj_id)) try: return self._objects[obj_id] except KeyError: raise NoSuchObject( 'No such object with ID: %s' % str(obj_id) ) def get_object_contents(self, obj): """ Returns all objects inside of the specified object. :param BaseObject obj: The object whose contents to calculate. :rtype: list :returns: A list of BaseObject instances whose current location is ``obj``. """ return [omatch for omatch in self._objects.values() if omatch.location == obj] def global_name_search(self, name): """ Does a global name search of all objects. Compares input to the name odata key on all objects. :param str name: The name to search for. :returns: A generator of ``BaseObject`` matches. """ #noinspection PyShadowingBuiltins for id, obj in self._objects.iteritems(): ratio = fuzz.partial_ratio(name, obj.name) if ratio > 50: yield obj def find_exits_linked_to_obj(self, obj): """ Finds all exits that are linked to the given object. :param BaseObject obj: The object which to find linked exits to. :rtype: list :return: A list of exits that are linked to the given object. """ if obj.base_type == 'exit': # Exits can't be linked to one another, this ends up being invalid. return [] # We could use a generator for this, but then we couldn't iterate # and delete as we went, as this would change the size of self._objects # during the iteration (causing an exception). So store in list. linked_exits = [] #noinspection PyShadowingBuiltins for id, db_obj in self._objects.iteritems(): if not db_obj.base_type == 'exit': # Not an exit, not interested. continue destination = db_obj.destination if destination and destination.id == obj.id: # This object's destination matches the specified object's ID. linked_exits.append(db_obj) return linked_exits def find_objects_in_zone(self, obj): """ Finds all objects whose zone master object is set to the given object. :param BaseObject obj: The object whose zone members to find. :rtype: list :return: A list of the object's zone members. """ # We could use a generator for this, but then we couldn't iterate # and delete as we went, as this would change the size of self._objects # during the iteration (causing an exception). So store in list. zone_members = [] #noinspection PyShadowingBuiltins for id, db_obj in self._objects.iteritems(): zone = db_obj.zone if zone and zone.id == obj.id: # This object's zone matches the specified object's ID. zone_members.append(db_obj) return zone_members @inlineCallbacks def empty_out_zone(self, obj): """ Given a ZMO, go through its member list and un-set the zone on each object. The end result will be a ZMO with no members. :param BaseObject obj: The ZMO whose members to un-set. :rtype: list :returns: The members that were removed from the zone. """ members = self.find_objects_in_zone(obj) for member in members: member.zone = None yield member.save() returnValue(members) @inlineCallbacks def raze_zone(self, obj): """ Given a ZMO, destroy all members and the ZMO itself. No survivors. :param BaseObject obj: The ZMO to completely eradicate. :rtype: list :returns: A list of IDs of the deleted objects (ZMO included). """ members = self.find_objects_in_zone(obj) deleted_ids = [] for member in members: deleted_ids.append(member.id) yield member.destroy() deleted_ids.append(obj.id) yield obj.destroy() returnValue(deleted_ids)
class ObjectStore(object): """ Serves as an in-memory object store for all "physical" entities in the game. An "object" can be stuff like a room or a thing. Objects are persisted to a DB via the :py:attr:`db_manager` attribute, which is a reference to a :py:class:`src.server.objects.db_io.DBManager` instance. .. note:: This class should know nothing about the DB that backs it. Make sure to keep any DB-related things out of here. """ def __init__(self, mud_service, db_mode='production'): """ :param MudService mud_service: The MudService class running the game. :keyword str mode: Either 'test' or 'production'. """ self._mud_service = mud_service # This is used to sub-classes of BaseObject. self.parent_loader = ParentLoader() # Keys are object IDs, values are the parent instances (children of # src.game.parents.base_objects.base.BaseObject) self._objects = {} # DB abstraction layer. self.db_manager = DBManager(mud_service, self.parent_loader, db_mode) @inlineCallbacks def prep_and_load(self): """ This runs early in server startup. Calls on the DBManager (self.db_manager) to prep the DB and load all objects. """ is_first_run = yield self.db_manager.prepare_and_load() if is_first_run: # If this is the first time the server has been started, we'll # need to create the starter room. parent_path = 'src.game.parents.base_objects.room.RoomObject' yield self.create_object(parent_path, name='And so it begins...') def loader_func(obj): """ This function runs on each object instantiated from the DB at start time. :param BaseObject obj: The object to load into the store. """ self._objects[obj.id] = obj yield self.db_manager.load_objects_into_store(loader_func) @inlineCallbacks def create_object(self, parent_path, name, **kwargs): """ Creates and saves a new object of the specified parent. :param str parent_path: The full Python path + class name for a parent. for example, src.game.parents.base_objects.room.RoomObject. :param str name: The name of the object. :param dict kwargs: Additional attributes to set on the object. :rtype: BaseObject :returns: The newly created/instantiated/saved object. """ #noinspection PyPep8Naming NewObject = self.parent_loader.load_parent(parent_path) obj = NewObject(self._mud_service, id=None, name=name, parent=parent_path, **kwargs) obj = yield self.save_object(obj) returnValue(obj) @inlineCallbacks def save_object(self, obj): """ Saves an object to the DB. :param BaseObject obj: The object to save to the DB. """ saved_obj = yield self.db_manager.save_object(obj) self._objects[saved_obj.id] = saved_obj returnValue(saved_obj) @inlineCallbacks def destroy_object(self, obj): """ Destroys an object by yanking it from :py:attr:`_objects` and the DB. :param BaseObject obj: The object to destroy. """ yield self.db_manager.destroy_object(obj) # Clear the object out of the store, mark it for GC. del self._objects[obj.id] del obj @inlineCallbacks def reload_object(self, obj): """ Re-loads the object from the DB. :param BaseObject obj: The object to re-load from the DB. :rtype: BaseObject :returns: The newly re-loaded object. """ reloaded_obj = yield self.db_manager.reload_object(obj) self._objects[reloaded_obj.id] = reloaded_obj returnValue(reloaded_obj) def get_object(self, obj_id): """ Given an object ID, return the object's instance. :param int obj_id: The ID of the object to return. :returns: The requested object, which will be a :class:`BaseObject` sub-class of some sort. :raises: :py:exc:`src.server.objects.exceptions.NoSuchObject` if no object with the requested ID exists. """ assert isinstance(obj_id, int), \ "get_object had a non-int passed: %s %s" % (obj_id, type(obj_id)) try: return self._objects[obj_id] except KeyError: raise NoSuchObject('No such object with ID: %s' % str(obj_id)) def get_object_contents(self, obj): """ Returns all objects inside of the specified object. :param BaseObject obj: The object whose contents to calculate. :rtype: list :returns: A list of BaseObject instances whose current location is ``obj``. """ return [ omatch for omatch in self._objects.values() if omatch.location == obj ] def global_name_search(self, name): """ Does a global name search of all objects. Compares input to the name odata key on all objects. :param str name: The name to search for. :returns: A generator of ``BaseObject`` matches. """ #noinspection PyShadowingBuiltins for id, obj in self._objects.iteritems(): ratio = fuzz.partial_ratio(name, obj.name) if ratio > 50: yield obj def find_exits_linked_to_obj(self, obj): """ Finds all exits that are linked to the given object. :param BaseObject obj: The object which to find linked exits to. :rtype: list :return: A list of exits that are linked to the given object. """ if obj.base_type == 'exit': # Exits can't be linked to one another, this ends up being invalid. return [] # We could use a generator for this, but then we couldn't iterate # and delete as we went, as this would change the size of self._objects # during the iteration (causing an exception). So store in list. linked_exits = [] #noinspection PyShadowingBuiltins for id, db_obj in self._objects.iteritems(): if not db_obj.base_type == 'exit': # Not an exit, not interested. continue destination = db_obj.destination if destination and destination.id == obj.id: # This object's destination matches the specified object's ID. linked_exits.append(db_obj) return linked_exits def find_objects_in_zone(self, obj): """ Finds all objects whose zone master object is set to the given object. :param BaseObject obj: The object whose zone members to find. :rtype: list :return: A list of the object's zone members. """ # We could use a generator for this, but then we couldn't iterate # and delete as we went, as this would change the size of self._objects # during the iteration (causing an exception). So store in list. zone_members = [] #noinspection PyShadowingBuiltins for id, db_obj in self._objects.iteritems(): zone = db_obj.zone if zone and zone.id == obj.id: # This object's zone matches the specified object's ID. zone_members.append(db_obj) return zone_members @inlineCallbacks def empty_out_zone(self, obj): """ Given a ZMO, go through its member list and un-set the zone on each object. The end result will be a ZMO with no members. :param BaseObject obj: The ZMO whose members to un-set. :rtype: list :returns: The members that were removed from the zone. """ members = self.find_objects_in_zone(obj) for member in members: member.zone = None yield member.save() returnValue(members) @inlineCallbacks def raze_zone(self, obj): """ Given a ZMO, destroy all members and the ZMO itself. No survivors. :param BaseObject obj: The ZMO to completely eradicate. :rtype: list :returns: A list of IDs of the deleted objects (ZMO included). """ members = self.find_objects_in_zone(obj) deleted_ids = [] for member in members: deleted_ids.append(member.id) yield member.destroy() deleted_ids.append(obj.id) yield obj.destroy() returnValue(deleted_ids)