PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/sgl/app/app.py

https://gitlab.com/brianguertin/worldedit
Python | 406 lines | 388 code | 16 blank | 2 comment | 8 complexity | 642945a67b171e49cf33a975dc630c8b MD5 | raw file
  1. import pygame
  2. from pygame.locals import *
  3. from sgl.video.display import Display
  4. import sgl.audio.sound as sound
  5. from copy import copy
  6. from sgl.core.containers import AttrDict
  7. from sgl.core.tuples import Point
  8. from sgl.core import xml, path
  9. from sgl.app.console import Console
  10. from sgl.app.events import *
  11. from sgl.gui.stack import Stack
  12. import sgl.core.log as logger
  13. from optparse import OptionParser, Option, OptionValueError
  14. import datetime
  15. from sgl.app.input import *
  16. def check_size_option(option, opt, value):
  17. try:
  18. if not value:
  19. return None
  20. else:
  21. w, h = map(int, value.split('x'))
  22. return w, h
  23. except:
  24. raise OptionValueError('option %s: invalid size value: %r' % (opt, value))
  25. class MyOption(Option):
  26. TYPES = Option.TYPES + ('size',)
  27. TYPE_CHECKER = copy(Option.TYPE_CHECKER)
  28. TYPE_CHECKER['size'] = check_size_option
  29. class _Frame:
  30. def __init__(self):
  31. self.app = None
  32. self.exit = False
  33. self.events = []
  34. self.ticks = 0
  35. self.count = 0
  36. self.time = 0
  37. @property
  38. def display(self):
  39. return self.app.display
  40. def to_event(self, e):
  41. if e.type == Event.MOUSEMOTION:
  42. r = EventObject(Event.MOUSEMOTION, pos=e.pos, rel=e.rel, button=e.buttons)
  43. elif e.type == Event.MOUSEBUTTONUP:
  44. r = EventObject(Event.MOUSEBUTTONUP, pos=e.pos, button=e.button)
  45. elif e.type == Event.MOUSEBUTTONDOWN:
  46. r = EventObject(Event.MOUSEBUTTONDOWN, pos=e.pos, button=e.button)
  47. else:
  48. r = DumbEvent(e)
  49. r.frame = self
  50. return r
  51. class EventObject(EventBase):
  52. def __init__(self, type, pos = (0,0), rel = (0,0), button = 0):
  53. self.type = type
  54. self.pos = Point(pos)
  55. self.rel = Point(rel)
  56. self.button = button
  57. @property
  58. def mod(self):
  59. return pygame.key.get_mods()
  60. def translate(self, v):
  61. r = copy(self)
  62. r.pos = self.pos + v
  63. return r
  64. class App:
  65. def __init__(self):
  66. self.console = Console()
  67. self.joysticks = []
  68. self.options = None
  69. self.set_default_options()
  70. pygame.mixer.pre_init(44100, -16, 2, 4096)
  71. pygame.init()
  72. for i in range(pygame.joystick.get_count()):
  73. j = pygame.joystick.Joystick(i)
  74. j.init()
  75. log('Joystick found: "%s"' % j.get_name(), type='Input')
  76. self.joysticks.append(j)
  77. self.display = Display()
  78. self.framerate = 60
  79. self.calculated_framerate = 0
  80. self.calculated_framerate_ticks = 0
  81. self.calculated_framerate_count = 0
  82. def set_default_options(self):
  83. display = xml.Element('display')
  84. # TODO: 'auto' display size that fits resolution when fullscreen
  85. # maybe seperate w/h for fullscreen
  86. display.set('width', '640')
  87. display.set('height', '480')
  88. display.set('fullscreen', 'false')
  89. audio = xml.Element('audio')
  90. audio.set('enabled', 'true')
  91. logger = xml.Element('log')
  92. logger.set('level', '2')
  93. logger.set('enable', 'all')
  94. logger.set('disable', '')
  95. e = xml.Element('options')
  96. e.append(display)
  97. e.append(audio)
  98. e.append(logger)
  99. self.options = e
  100. def init(self):
  101. self.display.init(self.options.find('display'))
  102. logger.disable(*self.options.find('log').get('disable').split(','))
  103. logger.set_logger(self.console.log)
  104. sound.ENABLED = self.options.find('audio').get('enabled')
  105. from sgl.gui.theme import Theme
  106. if not Theme.Global:
  107. Theme.Global = Theme()
  108. def run(self, fn):
  109. clock = pygame.time.Clock()
  110. frame = _Frame()
  111. frame.app = self
  112. while not frame.exit:
  113. self.tick(fn, frame, clock)
  114. def tick(self, fn, frame, clock):
  115. frame.exit = False
  116. fn(frame)
  117. self.console.draw(self.display)
  118. self.display.update()
  119. frame.ticks = clock.tick(self.framerate)
  120. frame.time += frame.ticks
  121. frame.count += 1
  122. frame.events = [frame.to_event(e) for e in pygame.event.get()]
  123. self.calculated_framerate_ticks += frame.ticks
  124. self.calculated_framerate_count += 1
  125. if self.calculated_framerate_ticks > 1000:
  126. self.calculated_framerate_ticks -= 1000
  127. self.calculated_framerate = self.calculated_framerate_count
  128. self.calculated_framerate_count = 0
  129. class Routine(object):
  130. def init(self):
  131. pass
  132. def deinit(self):
  133. pass
  134. def exit(self):
  135. parent = self.parent
  136. if len(parent.routines) and parent.routines[-1] == self:
  137. parent.pop()
  138. def input(self, e):
  139. pass
  140. def update(self, frame):
  141. pass
  142. def draw(self, display):
  143. pass
  144. def _main_loop(self, frame):
  145. d = self.app.display
  146. self.update(frame)
  147. self.draw(d)
  148. self.theme.drawInterface(self.gui, d)
  149. for e in frame.events:
  150. try:
  151. if self.root.input(e):
  152. e.stop()
  153. elif e.type == Event.QUIT:
  154. self.exit()
  155. e.stop()
  156. elif e.type == Event.KEYDOWN:
  157. if e.key == Key.ESCAPE:
  158. self.exit()
  159. e.stop()
  160. elif e.key == Key.BACKQUOTE:
  161. self.app.console.hidden = not self.app.console.hidden
  162. e.stop()
  163. self.input(e)
  164. except StopEvent:
  165. pass
  166. class Application(object):
  167. def __init__(self):
  168. from sgl.gui.controller import Controller
  169. from sgl.gui.theme import Theme
  170. self.app = App()
  171. Theme.Global = self.theme = Theme()
  172. self.gui = Stack()
  173. self.root = Controller(self.gui)
  174. self.routines = []
  175. self.services = []
  176. self.options = None
  177. self.args = []
  178. p = OptionParser(option_class=MyOption)
  179. p.add_option('-f', '--fullscreen', action='store_true', dest='fullscreen',
  180. help='run fullscreen')
  181. p.add_option('-w', '--window', action='store_false', dest='fullscreen',
  182. help='run windowed')
  183. p.add_option('-n', '--no-sound', action='store_true', dest='no_sound',
  184. help='disable audio')
  185. p.add_option('-v', '--verbose', action='store_true', dest='verbose',
  186. help='enable all log output')
  187. #p.add_option('-q', '--quiet', action='store_true', dest='quiet',
  188. # help='disable all log output')
  189. p.add_option('-p', '--profile', action='store_true', dest='profile',
  190. help='profile mode (slow)')
  191. p.add_option('-o', '--optimize', action='store_true', dest='optimize',
  192. help='optimize using psyco')
  193. p.add_option('-l', '--log-level', dest='log_level', type='int', default=0,
  194. help='print logs up to N verbosity (min 0, max 5)', metavar='N')
  195. p.add_option('--size', dest='display_size', type='size',
  196. default='', metavar='WIDTHxHEIGHT')
  197. p.add_option('--audio-frequency', dest='audio_frequency', type='int',
  198. default=0, metavar='FREQUENCY')
  199. p.add_option('--audio-buffer', dest='audio_frequency', type='int',
  200. default=0, metavar='BUFFER')
  201. self.parser = p
  202. self._init = False
  203. def parse(self):
  204. if self.options is None:
  205. self.options, self.args = self.parser.parse_args()
  206. if self.options.display_size:
  207. w, h = self.options.display_size
  208. self.app.options.find('display').set('width', w)
  209. self.app.options.find('display').set('height', h)
  210. if self.options.fullscreen is not None:
  211. self.app.options.find('display').set('fullscreen', ('false','true')[self.options.fullscreen])
  212. def saveConfig(self, filename = 'config.xml'):
  213. tree = xml.ElementTree()
  214. tree._root = self.app.options
  215. tree._root.cleanup()
  216. tree.write(filename)
  217. def loadConfig(self, filename = 'config.xml'):
  218. if path.exists(filename):
  219. self.app.options = xml.read(filename)
  220. def init(self):
  221. if not self._init:
  222. self.parse()
  223. self.app.init()
  224. self.gui.size = self.app.display.size
  225. self._init = True
  226. if self.options.optimize:
  227. try:
  228. import psyco
  229. psyco.full()
  230. log('Pysco optimization complete')
  231. except ImportError:
  232. pass
  233. def push(self, routine):
  234. from sgl.gui.panel import Panel
  235. routine.parent = self
  236. routine.app = self.app
  237. routine.gui = Panel()
  238. routine.root = self.root
  239. routine.theme = self.theme
  240. self.gui.add(routine.gui)
  241. routine.init()
  242. self.gui.pack()
  243. self.routines.append(routine)
  244. log('Starting %s' % routine.__class__.__name__)
  245. def pop(self):
  246. r = self.routines.pop()
  247. log('Stopping %s' % r.__class__.__name__)
  248. r.deinit()
  249. self.gui.pop()
  250. def switch(self, routine):
  251. self.pop()
  252. self.push(routine)
  253. def loop(self, routine = None):
  254. self.init()
  255. if routine:
  256. self.push(routine)
  257. if self.options.profile:
  258. import pstats, cProfile as profile
  259. profile.runctx('self._do_loop()', globals(), locals(), 'profile.bin')
  260. p = pstats.Stats('profile.bin')
  261. p.strip_dirs().sort_stats('cumulative').print_stats(20)
  262. else:
  263. self._do_loop()
  264. def _do_loop(self):
  265. clock = pygame.time.Clock()
  266. frame = _Frame()
  267. frame.app = self.app
  268. start_time = datetime.datetime.now()
  269. frame_count = 0
  270. while len(self.routines):
  271. r = self.routines[-1]
  272. self.app.tick(r._main_loop, frame, clock)
  273. if frame.exit:
  274. self.pop()
  275. frame_count += 1
  276. seconds = (datetime.datetime.now() - start_time).seconds
  277. log('%s frames in %s seconds. Frames per second: %s' % (frame_count, seconds, float(frame_count) / (seconds or 1)))
  278. class ConsoleApplication(object):
  279. def __init__(self):
  280. from sgl.core.terminal import Terminal
  281. self.idle = 30
  282. self.routines = []
  283. self.services = set()
  284. self.parser = OptionParser(option_class=MyOption)
  285. self.options = None
  286. self.args = []
  287. self.terminal = Terminal()
  288. def parse(self):
  289. if self.options is None:
  290. self.options, self.args = self.parser.parse_args()
  291. def init(self):
  292. self.parse()
  293. def loop(self, routine = None):
  294. self.init()
  295. if routine:
  296. self.push(routine)
  297. clock = pygame.time.Clock()
  298. frame = _Frame()
  299. event = EventBase()
  300. event.type = Event.KEYDOWN
  301. while len(self.routines):
  302. # Update background services
  303. for s in self.services:
  304. if (not hasattr(s, 'paused')) or (not s.paused):
  305. s.update(frame)
  306. r = self.routines[-1]
  307. for key in self.terminal.events:
  308. event.key = event.unicode = key
  309. r.input(event)
  310. r.update(frame)
  311. r.draw(self.terminal)
  312. frame.ticks = clock.tick(self.idle)
  313. frame.time += frame.ticks
  314. frame.count += 1
  315. if frame.exit:
  316. self.pop()
  317. def push(self, routine):
  318. routine.parent = self
  319. routine.init()
  320. self.routines.append(routine)
  321. log('Starting %s' % routine.__class__.__name__)
  322. def pop(self):
  323. r = self.routines.pop()
  324. log('Stopping %s' % r.__class__.__name__)
  325. r.deinit()
  326. def switch(self, routine):
  327. self.pop()
  328. self.push(routine)
  329. def exit(self):
  330. while len(self.routines):
  331. self.pop()
  332. def addService(self, service):
  333. service.parent = self
  334. service.init()
  335. self.services.add(service)
  336. def removeService(self, service):
  337. self.services = set([s for s in self.services if s is not service])