【问题标题】:Problem with room/screen/menu controller in python game: old rooms are not removed from memorypython游戏中房间/屏幕/菜单控制器的问题:旧房间没有从内存中删除
【发布时间】:2010-03-29 18:50:45
【问题描述】:

我真的是在用头撞墙(就像,是的,在身体上,在我当前的位置,我正在损坏我的头盖骨)。基本上,我有一个带有一些典型游戏“房间”或“屏幕”的 Python/Pygame 游戏。 EG 标题画面、高分画面和实际游戏室。当我在房间之间切换时发生了一些不好的事情:旧房间(及其各种项目)没有从内存或我的事件侦听器中删除。不仅如此,每次我回到某个房间时,我的事件侦听器数量都会增加,内存消耗也会增加!(所以如果我在标题屏幕和“游戏室”,例如,事件监听器的数量和内存使用量不断增加。

主要问题是所有事件侦听器开始累加并真正耗尽 CPU。我是 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   

【问题讨论】:

    标签: python performance event-handling pygame


    【解决方案1】:

    当你做这样的事情时:

    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 函数将必须检查房间是否已经创建。然后您可以创建一个新房间或智能加载旧房间。但是您还必须以某种方式跟踪这些房间。

    编辑:

    那好吧。问题不在于房间,而在于听众。您在 init 上注册的每个侦听器都应该在 del 上取消注册。我在您的 src 中搜索了“unregister_listener”,只发现它取消了房间监听器的注册。

    因此,当您创建 100 个按钮,然后再创建 100 个而不取消注册任何侦听器时,您将拥有 100 个孤立的侦听器。这不好。我会重载 __ del __ () 函数来删除这些侦听器,就像 __ init __ () 函数添加它们一样。

    这有意义吗?

    【讨论】:

    • 感谢 jjfine 的回答。不幸的是,我已经尝试了那个确切的代码(现在又试了一次),但它并没有解决问题。房间本身从侦听器中消失了,但向事件侦听器注册的所有房间属性仍然存在。我也尝试过“智能地加载旧的”,但这也没有奏效......我将在下面发布我尝试过的代码作为答案。
    • 谢谢 jjfine,我会试试的。不过,您是否注意到我的侦听器字典是弱引用字典?我很困惑,因为我认为这应该自动摆脱弱引用,所以我认为当它们不再被引用时它会摆脱一些东西,这就是为什么我没有明确取消注册听众。
    【解决方案2】:

    我还尝试跟踪房间,而不是在每次有新房间请求出现时丢弃它们并创建新房间。不幸的是,这段代码对我也不起作用:当我从游戏室返回标题屏幕时,标题屏幕呈现,但没有任何响应......我不确定问题出在哪里......

    观察听众的数量,这似乎并没有解决“蠕变”问题,因为它来自: 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()
    

    【讨论】:

      【解决方案3】:

      好吧,通过向我的事件管理器添加一个 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)
      

      虽然这似乎不是最好的方法。如果有人知道为什么this 不起作用,或者为什么即使我使用弱引用字典来保存侦听器,我的事件侦听器也必须手动清除,我很想听听。

      【讨论】:

        猜你喜欢
        • 2020-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多