Проблема с контроллером комнаты / экрана / меню в игре на Python: старые комнаты не удаляются из памяти - PullRequest
1 голос
/ 29 марта 2010

Я буквально бью головой о стену здесь (как, да, физически, в моем текущем местоположении, я повреждаю свой череп). По сути, у меня есть игра Python / Pygame с некоторыми типичными игровыми "комнатами" или "экранами". Например, титульный экран, экран рекордов и игровая комната. Когда я переключаюсь между комнатами, происходит что-то плохое: старая комната (и ее различные предметы) не удаляются из памяти или из моего прослушивателя событий. Не только это, но каждый раз, когда я возвращаюсь в определенную комнату, мое число слушателей событий увеличивается, а также расходуется ОЗУ! (Так что, если я перейду назад и вперед между экраном заголовка и «игровая комната», например, число слушателей событий и использование памяти просто продолжают расти.

Основная проблема заключается в том, что все слушатели событий начинают складываться и действительно истощают процессор. Я новичок в Python, и не знаю, делаю ли я что-то явно не так, или что.

Я буду любить тебя так сильно, если ты поможешь мне в этом!

Ниже приведен соответствующий исходный код. Полный исходный код на http://www.necessarygames.com/my_games/betraveled/betraveled_src0328.zip (Требуется Python 2.6 + Pygame 1.9)

MAIN.PY

class RoomController(object):
    """Controls which room is currently active (eg Title Screen)"""

    def __init__(self, screen, ev_manager):
        self.room = None
        self.screen = screen
        self.ev_manager = ev_manager
        self.ev_manager.register_listener(self)
        self.room = self.set_room(config.room)

    def set_room(self, room_const):
        #Unregister old room from ev_manager
        if self.room:
            self.room.ev_manager.unregister_listener(self.room)
            self.room = None
        #Set new room based on const
        if room_const == config.TITLE_SCREEN:
            return rooms.TitleScreen(self.screen, self.ev_manager)
        elif room_const == config.GAME_MODE_ROOM:
            return rooms.GameModeRoom(self.screen, self.ev_manager)        
        elif room_const == config.GAME_ROOM:
            return rooms.GameRoom(self.screen, self.ev_manager)
        elif room_const == config.HIGH_SCORES_ROOM:
            return rooms.HighScoresRoom(self.screen, self.ev_manager)

    def notify(self, event):
        if isinstance(event, ChangeRoomRequest):
            if event.game_mode:
                config.game_mode = event.game_mode            
            self.room = self.set_room(event.new_room)

#Run game 
def main():
    pygame.init()
    screen = pygame.display.set_mode(config.screen_size)

    ev_manager = EventManager()
    spinner = CPUSpinnerController(ev_manager)
    room_controller = RoomController(screen, ev_manager)    
    pygame_event_controller = PyGameEventController(ev_manager)

    spinner.run()



EVENT_MANAGER.PY

class EventManager:

    #This object is responsible for coordinating most communication
    #between the Model, View, and Controller.
    def __init__(self):
        from weakref import WeakKeyDictionary
        self.last_listeners = {}
        self.listeners = WeakKeyDictionary()
        self.eventQueue= []
        self.gui_app = None

    #----------------------------------------------------------------------
    def register_listener(self, listener):
        self.listeners[listener] = 1

    #----------------------------------------------------------------------
    def unregister_listener(self, listener):
        if listener in self.listeners:
            del self.listeners[listener]

    #----------------------------------------------------------------------
    def clear(self):
        del self.listeners[:]

    #----------------------------------------------------------------------
    def post(self, event):
#        if  isinstance(event, MouseButtonLeftEvent):
#            debug(event.name)
        #NOTE: copying the list like this before iterating over it, EVERY tick, is highly inefficient,
        #but currently has to be done because of how new listeners are added to the queue while it is running
        #(eg when popping cards from a deck). Should be changed. See: http://dr0id.homepage.bluewin.ch/pygame_tutorial08.html
        #and search for "Watch the iteration"

        print 'Number of listeners: ' + str(len(self.listeners))

        for listener in list(self.listeners):                               
            #NOTE: If the weakref has died, it will be 
            #automatically removed, so we don't have 
            #to worry about it.
            listener.notify(event)

    def notify(self, event):
        pass

#------------------------------------------------------------------------------
class PyGameEventController:
    """..."""
    def __init__(self, ev_manager):
        self.ev_manager = ev_manager
        self.ev_manager.register_listener(self) 
        self.input_freeze = False

    #----------------------------------------------------------------------
    def notify(self, incoming_event):

        if isinstance(incoming_event, UserInputFreeze):
            self.input_freeze = True

        elif isinstance(incoming_event, UserInputUnFreeze):
            self.input_freeze = False        

        elif isinstance(incoming_event, TickEvent) or isinstance(incoming_event, BoardCreationTick):

            #Share some time with other processes, so we don't hog the cpu
            pygame.time.wait(5)

            #Handle Pygame Events
            for event in pygame.event.get():
                #If this event manager has an associated PGU GUI app, notify it of the event
                if self.ev_manager.gui_app:
                    self.ev_manager.gui_app.event(event)
                #Standard event handling for everything else
                ev = None
                if event.type == QUIT:
                    ev = QuitEvent()
                elif event.type == pygame.MOUSEBUTTONDOWN and not self.input_freeze:
                    if event.button == 1:    #Button 1
                        pos = pygame.mouse.get_pos()
                        ev = MouseButtonLeftEvent(pos)
                elif event.type == pygame.MOUSEBUTTONDOWN and not self.input_freeze:
                    if event.button == 2:    #Button 2
                        pos = pygame.mouse.get_pos()
                        ev = MouseButtonRightEvent(pos)   
                elif event.type == pygame.MOUSEBUTTONUP and not self.input_freeze:
                    if event.button == 2:    #Button 2 Release
                        pos = pygame.mouse.get_pos()
                        ev = MouseButtonRightReleaseEvent(pos)                                              
                elif event.type == pygame.MOUSEMOTION:
                        pos = pygame.mouse.get_pos()
                        ev = MouseMoveEvent(pos)

                #Post event to event manager
                if ev:
                    self.ev_manager.post(ev)        

#        elif isinstance(event, BoardCreationTick):
#            #Share some time with other processes, so we don't hog the cpu
#            pygame.time.wait(5)               
#                           
#            #If this event manager has an associated PGU GUI app, notify it of the event
#            if self.ev_manager.gui_app:
#                self.ev_manager.gui_app.event(event)

#------------------------------------------------------------------------------            
class CPUSpinnerController:

    def __init__(self, ev_manager):
        self.ev_manager = ev_manager
        self.ev_manager.register_listener(self)
        self.clock = pygame.time.Clock()
        self.cumu_time = 0

        self.keep_going = True


    #----------------------------------------------------------------------
    def run(self):
        if not self.keep_going:
            raise Exception('dead spinner')        
        while self.keep_going: 
            time_passed = self.clock.tick()
            fps = self.clock.get_fps()
            self.cumu_time += time_passed
            self.ev_manager.post(TickEvent(time_passed, fps))

            if self.cumu_time >= 1000:
                self.cumu_time = 0
                self.ev_manager.post(SecondEvent(fps=fps))

        pygame.quit()


    #----------------------------------------------------------------------
    def notify(self, event):
        if isinstance(event, QuitEvent):
            #this will stop the while loop from running
            self.keep_going = False       



ПРИМЕР КЛАССА С ИСПОЛЬЗОВАНИЕМ МЕНЕДЖЕРА СОБЫТИЙ

class Timer(object):

    def __init__(self, ev_manager, time_left):
        self.ev_manager = ev_manager
        self.ev_manager.register_listener(self)
        self.time_left = time_left
        self.paused = False

    def __repr__(self):
        return str(self.time_left)

    def pause(self):
        self.paused = True

    def unpause(self):
        self.paused = False

    def notify(self, event):
        #Pause Event
        if isinstance(event, Pause):   
            self.pause() 
        #Unpause Event
        elif isinstance(event, Unpause):   
            self.unpause()                
        #Second Event
        elif isinstance(event, SecondEvent):   
            if not self.paused: 
                self.time_left -= 1   

Ответы [ 3 ]

1 голос
/ 29 марта 2010

Когда вы делаете что-то вроде этого:

return rooms.TitleScreen(self.screen, self.ev_manager) 

Я предполагаю, что вы создаете новый объект TitleScreen.

Если это то, что вы хотите сделать, то, возможно, вы захотите удалить старый объект комнаты при переключении комнат.

def notify(self, event):
  if isinstance(event, ChangeRoomRequest):
    if event.game_mode:
      config.game_mode = event.game_mode            
    del self.room  // delete the old room object
    self.room = self.set_room(event.new_room)

Если вы хотите, чтобы комнаты сохранялись, ваша функция set_room должна будет проверить, была ли комната уже создана. Затем вы можете создать новую комнату или разумно загрузить старую. Но вам также придется как-то следить за этими комнатами.

EDIT:

Хорошо, тогда. Проблема не в комнатах, а в слушателях. Каждый слушатель, которого вы зарегистрировали на init , вероятно, должен быть незарегистрированным на del . Я выполнил поиск 'unregister_listener' в вашем src и обнаружил, что он не регистрирует слушателей комнаты.

Таким образом, когда вы создаете 100 кнопок, а затем создаете еще 100 без отмены регистрации слушателей, у вас будет 100 осиротевших слушателей. Это не хорошо. Я бы перегрузил функцию __ del __ (), чтобы удалить этих слушателей так же, как их добавляет функция __ init __ ().

Имеет ли это смысл?

0 голосов
/ 30 марта 2010

Ну, временно «решил» это, добавив функцию clear () в мой менеджер событий и вызывая ее перед каждым переключением комнаты, очищая все слушатели, кроме трех моих контроллеров:

def clear(self):    
    for listener in list(self.listeners):
        if not isinstance(listener, CPUSpinnerController):  
            if not isinstance(listener, RoomController):  
                if not isinstance(listener, PyGameEventController):  
                    self.unregister_listener(listener)

Хотя это не лучший метод. Если у кого-то есть понимание того, почему этот не работает, или почему мой слушатель событий должен быть очищен вручную, даже если я использую слабый справочный словарь для удержания слушателей, я бы с удовольствием услышать.

0 голосов
/ 30 марта 2010

Я также пытался отслеживать комнаты, вместо того, чтобы бросать их и создавать новые каждый раз, когда приходит новый запрос комнаты. К сожалению, этот код также не работает для меня: когда я возвращаюсь на титульный экран из игровой комнаты, титульный экран рендерится, но ничто не отвечает ни на что ... Я не уверен, в чем проблема ...

Наблюдая за количеством слушателей, кажется, что это не решает проблему "ползучести", как это происходит из: 1) Титульный экран: 4 активных слушателя, чтобы: 2) Экран выбора игрового режима: 5 слушателей активны, чтобы: 3) Игровая комната: 86 активных слушателей, для: 4) Заголовок экрана (не отвечает): 100 слушателей активны

Вот код, который я пробовал в MAIN.PY

class RoomController(object):
    """Controls which room is currently active (eg Title Screen)"""

    def __init__(self, screen, ev_manager):
        self.room = None
        self.screen = screen
        self.ev_manager = ev_manager
        self.ev_manager.register_listener(self)

        self.title_screen = None
        self.game_mode_room = None
        self.game_room = None
        self.high_scores_room = None

        self.room = self.set_room(config.room)

    def set_room(self, room_const):
        #Set new room based on const
        if room_const == config.TITLE_SCREEN:
            if self.title_screen == None:
                self.title_screen = rooms.TitleScreen(self.screen, self.ev_manager)
            return self.title_screen
        elif room_const == config.GAME_MODE_ROOM:
            if self.game_mode_room == None:
                self.game_mode_room = rooms.GameModeRoom(self.screen, self.ev_manager) 
            return self.game_mode_room        
        elif room_const == config.GAME_ROOM:
            if self.game_room == None:
                self.game_room = rooms.GameRoom(self.screen, self.ev_manager)
            return self.game_room
        elif room_const == config.HIGH_SCORES_ROOM:
            if self.high_scores_room == None:
                self.high_scores_room = rooms.HighScoresRoom(self.screen, self.ev_manager)
            return self.high_scores_room 

    def notify(self, event):
        if isinstance(event, TickEvent):        
            self.render(self.screen) 
            pygame.display.update()
        elif isinstance(event, SecondEvent):
            pygame.display.set_caption(''.join(['FPS: ', str(int(event.fps))]))          
        elif isinstance(event, ChangeRoomRequest):
            if event.game_mode:
                config.game_mode = event.game_mode        
            self.room = self.set_room(event.new_room)

    def render(self, surface):
        self.room.render(surface)

def main():
    pygame.init()
    screen = pygame.display.set_mode(config.screen_size)

    ev_manager = EventManager()
    spinner = CPUSpinnerController(ev_manager)
    room_controller = RoomController(screen, ev_manager)    
    pygame_event_controller = PyGameEventController(ev_manager)

    spinner.run()


# this runs the main function if this script is called to run.
#  If it is imported as a module, we don't run the main function.
if __name__ == "__main__": 
#    cProfile.run('main()', 'cprofile')
    main()
...