我实际上是把头撞在这里的墙上(就像在物理上,在我现在的位置,我正在损坏我的头盖骨)。基本上,我有一个Python / Pygame游戏,有一些典型的游戏“房间”或“屏幕”。 EG标题屏幕,高分屏幕和实际游戏室。当我在房间之间切换时发生了一些不好的事情:旧房间(及其各种物品)不会从内存或我的事件监听器中删除。不仅如此,而且每次回到某个房间,我的事件监听器数量都会增加,RAM也会被消耗掉!(所以如果我在标题屏幕和例如,“游戏室”,事件监听器的数量和内存使用量不断上升。
主要问题是所有事件侦听器都开始加起来并且真的耗尽了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
答案 0 :(得分: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功能将需要检查房间是否已经创建。然后,您可以创建一个新房间或智能地加载旧房间。但你也必须以某种方式跟踪这些房间。
编辑:
那好吧。问题不是房间,而是听众。您在 del 上注册的每个听众都应该在 del 上取消注册。我在你的src中搜索了'unregister_listener',但发现它只取消注册房间监听器。
因此,当您创建100个按钮然后再创建100个按钮而不取消注册任何侦听器时,您将拥有100个孤立的侦听器。这不好。我重载__ del __()函数来删除那些监听器,就像__ init __()函数添加它们一样。
这有意义吗?
答案 1 :(得分:0)
我也试过跟踪房间,而不是在每次有新的房间要求时抛出它们并创建新房间。不幸的是,这段代码对我来说也不起作用:当我从游戏室返回标题画面时,标题画面呈现,但没有任何反应......我不确定那里有什么问题......
观察听众的数量,这似乎并没有解决“蠕变”问题,因为它来自: 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()
答案 2 :(得分:0)
好吧,通过向我的事件管理器添加一个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无效的原因有任何了解,或者为什么我的事件监听器必须手动清除,即使我使用弱引用字典来抓住听众,我也很乐意听到它。