# HG changeset patch # User Emmanuel Gil Peyrot # Date 1374001635 -7200 # Node ID 52829ebe2561d94145d334bae94388826d9a603f # Parent b1248bab2d0f69eb1fe73470044ddb1af798b1b9 Refactor window management in its own class. diff --git a/eosd b/eosd --- a/eosd +++ b/eosd @@ -54,6 +54,7 @@ import logging import pyximport pyximport.install() +from pytouhou.ui.window import Window from pytouhou.resource.loader import Loader from pytouhou.game.background import Background from pytouhou.ui.gamerunner import GameRunner @@ -107,6 +108,8 @@ def main(path, data, stage_num, rank, ch sys.stderr.write('Some data files were not found, did you forget the -p option?\n') exit(1) + window = Window(double_buffer=(not single_buffer), fps_limit=fps_limit, fixed_pipeline=fixed_pipeline) + if stage_num is None: story = True stage_num = 1 @@ -145,7 +148,7 @@ def main(path, data, stage_num, rank, ch game_class = EoSDGameBossRush if boss_rush else EoSDGame - runner = GameRunner(resource_loader, fps_limit=fps_limit, double_buffer=(not single_buffer), fixed_pipeline=fixed_pipeline, skip=skip_replay) + runner = GameRunner(window, resource_loader, skip=skip_replay) while True: if replay: level = replay.levels[stage_num - 1] @@ -200,8 +203,9 @@ def main(path, data, stage_num, rank, ch # Main loop runner.load_game(game, background, stage.bgms, replay, save_keystates) + window.set_runner(runner) try: - runner.start() + window.run() break except NextStage: if not story or stage_num == (7 if boss_rush else 6 if rank > 0 else 5): diff --git a/pytouhou/lib/sdl.pxd b/pytouhou/lib/sdl.pxd --- a/pytouhou/lib/sdl.pxd +++ b/pytouhou/lib/sdl.pxd @@ -51,6 +51,8 @@ cdef extern from "SDL_video.h": void SDL_GL_DeleteContext(SDL_GLContext context) void SDL_DestroyWindow(SDL_Window *window) + void SDL_SetWindowSize(SDL_Window *window, int w, int h) + cdef extern from "SDL_scancode.h": ctypedef enum SDL_Scancode: diff --git a/pytouhou/lib/sdl.pyx b/pytouhou/lib/sdl.pyx --- a/pytouhou/lib/sdl.pyx +++ b/pytouhou/lib/sdl.pyx @@ -65,6 +65,9 @@ cdef class Window: def gl_delete_context(self): SDL_GL_DeleteContext(self.context) + def set_window_size(self, width, height): + SDL_SetWindowSize(self.window, width, height) + cdef class Surface: cdef SDL_Surface *surface diff --git a/pytouhou/ui/gamerenderer.py b/pytouhou/ui/gamerenderer.py --- a/pytouhou/ui/gamerenderer.py +++ b/pytouhou/ui/gamerenderer.py @@ -29,24 +29,8 @@ from .renderer import Renderer class GameRenderer(Renderer): - __slots__ = ('game', 'background') - - def __init__(self, resource_loader, game=None, background=None): + def __init__(self, resource_loader): Renderer.__init__(self, resource_loader) - if game: - self.load_game(game, background) - - - def load_game(self, game=None, background=None): - self.game = game - self.background = background - - if game: - # Preload textures - self.texture_manager.preload(game.resource_loader.instanced_anms.values()) - - if background: - self.prerender_background(background) def render(self): diff --git a/pytouhou/ui/gamerunner.py b/pytouhou/ui/gamerunner.py --- a/pytouhou/ui/gamerunner.py +++ b/pytouhou/ui/gamerunner.py @@ -14,14 +14,9 @@ from pytouhou.lib import sdl -from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable, glDisable, - glHint, glEnableClientState, glViewport, glScissor, - glLoadMatrixf, glGenBuffers, glDeleteBuffers, - GL_MODELVIEW, GL_PROJECTION, - GL_TEXTURE_2D, GL_BLEND, GL_FOG, - GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST, - GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY, - GL_SCISSOR_TEST) +from pyglet.gl import (glMatrixMode, glEnable, glDisable, glViewport, + glScissor, glLoadMatrixf, glGenBuffers, glDeleteBuffers, + GL_MODELVIEW, GL_PROJECTION, GL_FOG, GL_SCISSOR_TEST) from pytouhou.utils.helpers import get_logger from pytouhou.utils.maths import perspective, setup_camera, ortho_2d @@ -36,83 +31,19 @@ from ctypes import c_uint, byref logger = get_logger(__name__) -class Clock(object): - def __init__(self, fps=None): - self._target_fps = 0 - self._ref_tick = 0 - self._ref_frame = 0 - self._fps_tick = 0 - self._fps_frame = 0 - self._rate = 0 - self.set_target_fps(fps) - - - def set_target_fps(self, fps): - self._target_fps = fps - self._ref_tick = 0 - self._fps_tick = 0 - - - def get_fps(self): - return self._rate - - - def tick(self): - current = sdl.get_ticks() - - if not self._ref_tick: - self._ref_tick = current - self._ref_frame = 0 - - if self._fps_frame >= (self._target_fps or 60): - self._rate = self._fps_frame * 1000. / (current - self._fps_tick) - self._fps_tick = current - self._fps_frame = 0 - - self._ref_frame += 1 - self._fps_frame += 1 +class GameRunner(GameRenderer): + def __init__(self, window, resource_loader, replay=None, skip=False): + GameRenderer.__init__(self, resource_loader) - target_tick = self._ref_tick - if self._target_fps: - target_tick += int(self._ref_frame * 1000 / self._target_fps) - - if current <= target_tick: - sdl.delay(target_tick - current) - else: - self._ref_tick = current - self._ref_frame = 0 - - -class GameRunner(GameRenderer): - def __init__(self, resource_loader, game=None, background=None, replay=None, double_buffer=True, fps_limit=60, fixed_pipeline=False, skip=False): - GameRenderer.__init__(self, resource_loader, game, background) - - sdl.init(sdl.INIT_VIDEO) - sdl.img_init(sdl.INIT_PNG) - sdl.mix_init(0) - - sdl.gl_set_attribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2) - sdl.gl_set_attribute(sdl.GL_CONTEXT_MINOR_VERSION, 1) - sdl.gl_set_attribute(sdl.GL_DOUBLEBUFFER, int(double_buffer)) - sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24) - - self.width, self.height = (game.interface.width, game.interface.height) if game else (640, 480) - self.win = sdl.Window('PyTouhou', - sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, - self.width, self.height, - sdl.WINDOW_OPENGL | sdl.WINDOW_SHOWN) - self.win.gl_create_context() - - sdl.mix_open_audio(44100, sdl.DEFAULT_FORMAT, 2, 4096) - sdl.mix_allocate_channels(26) #TODO: make it dependent on the SFX number. - - self.fps_limit = fps_limit - self.use_fixed_pipeline = fixed_pipeline + self.window = window self.replay_level = None self.skip = skip - self.has_exit = False self.keystate = 0 + self.use_fixed_pipeline = window.use_fixed_pipeline #XXX + self.width = window.width #XXX + self.height = window.height #XXX + if not self.use_fixed_pipeline: self.game_shader = GameShader() self.background_shader = BackgroundShader() @@ -122,14 +53,16 @@ class GameRunner(GameRenderer): glGenBuffers(2, vbo_array) self.vbo, self.back_vbo = vbo_array - if game: - self.load_game(game, background, replay) - - self.clock = Clock(self.fps_limit) - def load_game(self, game=None, background=None, bgms=None, replay=None, save_keystates=None): - GameRenderer.load_game(self, game, background) + self.game = game + self.background = background + + self.texture_manager.preload(game.resource_loader.instanced_anms.values()) + + if background: + self.prerender_background(background) + self.set_input(replay) if replay and replay.levels[game.stage - 1]: game.players[0].state.lives = self.replay_level.lives @@ -152,25 +85,11 @@ class GameRunner(GameRenderer): self.keys = self.replay_level.iter_keystates() - def set_size(self, width, height): - self.win.set_window_size(width, height) - - - def start(self, width=None, height=None): - width = width or (self.game.interface.width if self.game else 640) - height = height or (self.game.interface.height if self.game else 480) + def start(self): + width = self.game.interface.width if self.game else 640 + height = self.game.interface.height if self.game else 480 if (width, height) != (self.width, self.height): - self.set_size(width, height) - - # Initialize OpenGL - glEnable(GL_BLEND) - if self.use_fixed_pipeline: - glEnable(GL_TEXTURE_2D) - glHint(GL_FOG_HINT, GL_NICEST) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) - glEnableClientState(GL_COLOR_ARRAY) - glEnableClientState(GL_VERTEX_ARRAY) - glEnableClientState(GL_TEXTURE_COORD_ARRAY) + self.window.set_size(width, height) self.proj = perspective(30, float(self.game.width) / float(self.game.height), 101010101./2010101., 101010101./10101.) @@ -178,26 +97,13 @@ class GameRunner(GameRenderer): self.game_mvp = game_view * self.proj self.interface_mvp = ortho_2d(0., float(self.width), float(self.height), 0.) - while not self.has_exit: - if not self.skip: - self.update() - self.render_game() - self.render_interface() - self.win.gl_swap_window() - self.clock.tick() - else: - self.update() - if not self.use_fixed_pipeline: - vbo_array = (c_uint * 2)(self.vbo, self.back_vbo) - glDeleteBuffers(2, vbo_array) - - self.win.gl_delete_context() - self.win.destroy_window() - sdl.mix_close_audio() - sdl.mix_quit() - sdl.img_quit() - sdl.quit() + def finish(self): + #TODO: actually clean after buffers are not needed anymore. + #if not self.use_fixed_pipeline: + # vbo_array = (c_uint * 2)(self.vbo, self.back_vbo) + # glDeleteBuffers(2, vbo_array) + pass def update(self): @@ -208,9 +114,9 @@ class GameRunner(GameRenderer): if type_ == sdl.KEYDOWN: scancode = event[1] if scancode == sdl.SCANCODE_ESCAPE: - self.has_exit = True #TODO: implement the pause. + return False #TODO: implement the pause. elif type_ == sdl.QUIT: - self.has_exit = True + return False if self.game: if not self.replay_level: #TODO: allow user settings @@ -246,6 +152,10 @@ class GameRunner(GameRenderer): self.save_keystates.append(keystate) self.game.run_iter(keystate) + if not self.skip: + self.render_game() + self.render_interface() + return True def render_game(self): @@ -263,7 +173,7 @@ class GameRunner(GameRenderer): def render_interface(self): interface = self.game.interface - interface.labels['framerate'].set_text('%.2ffps' % self.clock.get_fps()) + interface.labels['framerate'].set_text('%.2ffps' % self.window.clock.get_fps()) if self.use_fixed_pipeline: glMatrixMode(GL_MODELVIEW) diff --git a/pytouhou/ui/window.py b/pytouhou/ui/window.py new file mode 100644 --- /dev/null +++ b/pytouhou/ui/window.py @@ -0,0 +1,146 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2013 Emmanuel Gil Peyrot +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + + +from pytouhou.lib import sdl + +from pyglet.gl import (glEnable, glHint, glEnableClientState, GL_TEXTURE_2D, + GL_BLEND, GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, + GL_NICEST, GL_COLOR_ARRAY, GL_VERTEX_ARRAY, + GL_TEXTURE_COORD_ARRAY) + + +class Clock(object): + def __init__(self, fps=None): + self._target_fps = 0 + self._ref_tick = 0 + self._ref_frame = 0 + self._fps_tick = 0 + self._fps_frame = 0 + self._rate = 0 + self.set_target_fps(fps) + + + def set_target_fps(self, fps): + self._target_fps = fps + self._ref_tick = 0 + self._fps_tick = 0 + + + def get_fps(self): + return self._rate + + + def tick(self): + current = sdl.get_ticks() + + if not self._ref_tick: + self._ref_tick = current + self._ref_frame = 0 + + if self._fps_frame >= (self._target_fps or 60): + self._rate = self._fps_frame * 1000. / (current - self._fps_tick) + self._fps_tick = current + self._fps_frame = 0 + + self._ref_frame += 1 + self._fps_frame += 1 + + target_tick = self._ref_tick + if self._target_fps: + target_tick += int(self._ref_frame * 1000 / self._target_fps) + + if current <= target_tick: + sdl.delay(target_tick - current) + else: + self._ref_tick = current + self._ref_frame = 0 + + + +class Window(object): + def __init__(self, size=None, double_buffer=True, fps_limit=60, + fixed_pipeline=False, sound=True): + self.fps_limit = fps_limit + self.use_fixed_pipeline = fixed_pipeline + self.runner = None + + sdl.init(sdl.INIT_VIDEO) + sdl.img_init(sdl.INIT_PNG) + if sound: + sdl.mix_init(0) + + sdl.gl_set_attribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2) + sdl.gl_set_attribute(sdl.GL_CONTEXT_MINOR_VERSION, 1) + sdl.gl_set_attribute(sdl.GL_DOUBLEBUFFER, int(double_buffer)) + sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24) + + self.width, self.height = size if size else (640, 480) + + self.win = sdl.Window('PyTouhou', + sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, + self.width, self.height, + sdl.WINDOW_OPENGL | sdl.WINDOW_SHOWN) + self.win.gl_create_context() + + # Initialize OpenGL + glEnable(GL_BLEND) + if self.use_fixed_pipeline: + glEnable(GL_TEXTURE_2D) + glHint(GL_FOG_HINT, GL_NICEST) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) + glEnableClientState(GL_COLOR_ARRAY) + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_TEXTURE_COORD_ARRAY) + + # Initialize sound + if sound: + sdl.mix_open_audio(44100, sdl.DEFAULT_FORMAT, 2, 4096) + sdl.mix_allocate_channels(26) #TODO: make it dependent on the SFX number. + + self.clock = Clock(self.fps_limit) + + + def set_size(self, width, height): + self.win.set_window_size(width, height) + + + def set_runner(self, runner): + self.runner = runner + runner.start() + + + def run(self): + try: + while self.run_frame(): + pass + finally: + self.runner.finish() + + + def run_frame(self): + if self.runner: + running = self.runner.update() + self.win.gl_swap_window() + self.clock.tick() + return running + + + def __dealloc__(self): + self.win.gl_delete_context() + self.win.destroy_window() + sdl.mix_close_audio() + sdl.mix_quit() + sdl.img_quit() + sdl.quit()