# -*- coding: utf-8 -*- # # Copyright © 2019 Simon Forman # # This file is part of Thun # # Thun 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, either version 3 of the License, or # (at your option) any later version. # # Thun 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. # # You should have received a copy of the GNU General Public License # along with Thun. If not see . # ''' Main Module ====================================== Pulls everything together. ''' from __future__ import print_function from __future__ import division from past.builtins import execfile from builtins import object from past.utils import old_div import os, sys, traceback import pygame from joy.library import initialize, DefinitionWrapper, SimpleFunctionWrapper from joy.vui import core, display, persist_task FULLSCREEN = '-f' in sys.argv JOY_HOME = os.environ.get('JOY_HOME') if JOY_HOME is None: JOY_HOME = os.path.expanduser('~/.thun') if not os.path.isabs(JOY_HOME): raise ValueError('what directory?') def load_definitions(pt, dictionary): '''Load definitions from ``definitions.txt``.''' lines = pt.open('definitions.txt')[1] for line in lines: if '==' in line: DefinitionWrapper.add_def(line, dictionary) def load_primitives(home, name_space): '''Load primitives from ``library.py``.''' fn = os.path.join(home, 'library.py') if os.path.exists(fn): execfile(fn, name_space) def init(): ''' Initialize the system. * Init PyGame * Create main window * Start the PyGame clock * Set the event mask * Create the PersistTask ''' print('Initializing Pygame...') pygame.init() print('Creating window...') if FULLSCREEN: screen = pygame.display.set_mode() else: screen = pygame.display.set_mode((1024, 768)) clock = pygame.time.Clock() pygame.event.set_allowed(None) pygame.event.set_allowed(core.ALLOWED_EVENTS) pt = persist_task.PersistTask(JOY_HOME) return screen, clock, pt def init_context(screen, clock, pt): ''' More initialization * Create the Joy dictionary * Create the Display * Open the log, menu, and scratch text viewers, and the stack pickle * Start the main loop * Create the World object * Register PersistTask and World message handlers with the Display * Load user function definitions. ''' D = initialize() d = display.Display( screen, D.__contains__, *((144 - 89, 144, 89) if FULLSCREEN else (89, 144)) ) log = d.init_text(pt, 0, 0, 'log.txt') tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt') t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt') loop = core.TheLoop(d, clock) stack_id, stack_holder = pt.open('stack.pickle') world = core.World(stack_id, stack_holder, D, d.broadcast, log) loop.install_task(pt.task_run, 10000) # save files every ten seconds d.handlers.append(pt.handle) d.handlers.append(world.handle) load_definitions(pt, D) return locals() def error_guard(loop, n=10): ''' Run a loop function, retry for ``n`` exceptions. Prints tracebacks on ``sys.stderr``. ''' error_count = 0 while error_count < n: try: loop() break except: traceback.print_exc(file=sys.stderr) error_count += 1 class FileFaker(object): '''Pretends to be a file object but writes to log instead.''' def __init__(self, log): self.log = log def write(self, text): '''Write text to log.''' self.log.append(text) def flush(self): pass def main(screen, clock, pt): ''' Main function. * Call ``init_context()`` * Load primitives * Create an ``evaluate`` function that lets you just eval some Python code * Redirect ``stdout`` to the log using a ``FileFaker`` object, and... * Start the main loop. ''' name_space = init_context(screen, clock, pt) load_primitives(pt.home, name_space.copy()) @SimpleFunctionWrapper def evaluate(stack): '''Evaluate the Python code text on the top of the stack.''' code, stack = stack exec(code, name_space.copy()) return stack name_space['D']['evaluate'] = evaluate sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout try: error_guard(name_space['loop'].loop) finally: sys.stdout = old_stdout return name_space['d']