def post_import(): """ This is called after import or reload, to do further initialization of various modules. """ import renpy # @UnresolvedImport # Create the store. renpy.python.create_store("store") # Import the contents of renpy.defaultstore into renpy.store, and set # up an alias as we do. renpy.store = sys.modules['store'] renpy.exports.store = renpy.store sys.modules['renpy.store'] = sys.modules['store'] import subprocess sys.modules['renpy.subprocess'] = subprocess for k, v in six.iteritems(renpy.defaultstore.__dict__): renpy.store.__dict__.setdefault(k, v) renpy.store.eval = renpy.defaultstore.eval # Import everything into renpy.exports, provided it isn't # already there. for k, v in six.iteritems(globals()): vars(renpy.exports).setdefault(k, v)
def call_replay(label, scope={}): """ :doc: replay Calls a label as a memory. Keyword arguments are used to set the initial values of variables in the memory context. """ renpy.game.log.complete() old_log = renpy.game.log renpy.game.log = renpy.python.RollbackLog() sb = renpy.python.StoreBackup() renpy.python.clean_stores() context = renpy.execution.Context(True) contexts.append(context) if renpy.display.interface is not None: renpy.display.interface.enter_context() # This has to be here, to ensure the scope stuff works. renpy.exports.execute_default_statement() for k, v in six.iteritems(renpy.config.replay_scope): setattr(renpy.store, k, v) for k, v in six.iteritems(scope): setattr(renpy.store, k, v) renpy.store._in_replay = label try: context.goto_label("_start_replay") renpy.execution.run_context(False) except EndReplay: pass finally: context.pop_all_dynamic() contexts.pop() renpy.game.log = old_log sb.restore() if interface and interface.restart_interaction and contexts: contexts[-1].scene_lists.focused = None renpy.config.skipping = None if renpy.config.after_replay_callback: renpy.config.after_replay_callback()
def init(): """ Loads the persistent data from disk. This performs the initial load of persistent data from the local disk, so that we can configure the savelocation system. """ filename = os.path.join(renpy.config.savedir, "persistent.new") persistent = load(filename) if persistent is None: filename = os.path.join(renpy.config.savedir, "persistent") persistent = load(filename) if persistent is None: persistent = Persistent() # Create the backup of the persistent data. v = vars(persistent) for k, v in six.iteritems(vars(persistent)): backup[k] = safe_deepcopy(v) return persistent
def check_styles(): for full_name, s in six.iteritems(renpy.style.styles): # @UndefinedVariable name = "style." + full_name[0] for i in full_name[1:]: name += "[{!r}]".format(i) check_style("Style " + name, s)
def restore(self): """ Restores the modules to a state similar to the state of the modules when the backup was created. """ if not self.names: return # Remove new variables from the module. for mod, names in six.iteritems(self.names): modvars = vars(mod) for name in set(modvars.keys()) - names: del modvars[name] objects = cPickle.loads(self.objects_pickle) for k, v in six.iteritems(self.variables): mod, field = k setattr(mod, field, objects[v])
def pop_dynamic_roots(self, roots): for dynamic in reversed(self.dynamic_stack): for k, v in six.iteritems(dynamic): name = "store." + k if isinstance(v, Delete) and (name in roots): del roots[name] else: roots[name] = v
def diff_memory(update=True): """ :doc: memory Profiles objects, surface, and texture memory use by Ren'Py and the game. Writes (to memory.txt and stdout) the difference in memory usage from the last time this function was called with `update` true. The accounting is by names in the store and in the Ren'Py implementation that the memory is reachable from. If an object is reachable from more than one name, it's assigned to the name it's most directly reachable from. As it has to scan all memory used by Ren'Py, this function may take a long time to complete. """ global old_usage global old_total write("=" * 78) write("") write("Memory diff at " + time.ctime() + ":") write("") usage = profile_memory_common()[0] total = sum(usage.values()) diff = [] for k, v in six.iteritems(usage): diff.append((v - old_usage.get(k, 0), k)) diff.sort() for change, name in diff: if name == "renpy.memory.old_usage": continue if change: write("{:+14,d} {:13,d} {}".format(change, usage[name], name)) write("-" * 14 + " " + "-" * 13) write("{:+14,d} {:13,d} {}".format(total - old_total, total, "Total memory usage (in bytes).")) write("") if update: old_usage = usage old_total = total
def check_style(name, s): for p in s.properties: for k, v in six.iteritems(p): # Treat font specially. if k.endswith("font"): if isinstance(v, renpy.text.font.FontGroup): for f in set(v.map.values()): check_file(name, f) else: check_file(name, v) if isinstance(v, renpy.display.core.Displayable): check_style_property_displayable(name, k, v)
def backup_module(self, mod): """ Makes a backup of `mod`, which must be a Python module. """ try: name = mod.__name__ except: return if not name.startswith("renpy"): return if name in backup_blacklist: return if name.startswith("renpy.styledata"): return self.names[mod] = set(vars(mod).keys()) for k, v in six.iteritems(vars(mod)): if k.startswith("__") and k.endswith("__"): continue if isinstance(v, type_blacklist): continue if name + "." + k in name_blacklist: continue idv = id(v) self.variables[mod, k] = idv self.objects[idv] = v # If we have a problem pickling things, uncomment the next block. try: cPickle.dumps(v, cPickle.HIGHEST_PROTOCOL) except: print("Cannot pickle", name + "." + k, "=", repr(v)) print("Reduce Ex is:", repr(v.__reduce_ex__(cPickle.HIGHEST_PROTOCOL)))
def pop_dynamic(self): """ Pops one level of the dynamic stack. Called when the return statement is run. """ if not self.dynamic_stack: return store = renpy.store.__dict__ dynamic = self.dynamic_stack.pop() for k, v in six.iteritems(dynamic): if isinstance(v, Delete): store.pop(k, None) else: store[k] = v
def split_properties(properties, *prefixes): """ :doc: other Splits up `properties` into multiple dictionaries, one per `prefix`. This function checks each key in properties against each prefix, in turn. When a prefix matches, the prefix is stripped from the key, and the resulting key is mapped to the value in the corresponding dictionary. If no prefix matches, an exception is thrown. (The empty string, "", can be used as the last prefix to create a catch-all dictionary.) For example, this splits properties beginning with text from those that do not:: text_properties, button_properties = renpy.split_properties(properties, "text_", "") """ rv = [] for _i in prefixes: rv.append({}) if not properties: return rv prefix_d = list(zip(prefixes, rv)) for k, v in six.iteritems(properties): for prefix, d in prefix_d: if k.startswith(prefix): d[k[len(prefix):]] = v break else: raise Exception( "Property {} begins with an unknown prefix.".format(k)) return rv
def visit(o, path): ido = id(o) if ido in seen: return seen.add(ido) if isinstance(o, (int, float, type(None), type)): return if isinstance(o, (tuple, list)): for i, oo in enumerate(o): rv = visit(oo, "{0}[{1!r}]".format(path, i)) if rv is not None: return rv elif isinstance(o, dict): for k, v in six.iteritems(o): rv = visit(v, "{0}[{1!r}]".format(path, k)) if rv is not None: return rv elif isinstance(o, types.MethodType): return visit(o.__self__, path + ".im_self") elif isinstance(o, types.ModuleType): return "{} = {}".format(path, repr(o)[:160]) else: try: reduction = o.__reduce_ex__(2) except: import copy try: copy.copy(o) return None except: pass return "{} = {}".format(path, repr(o)[:160]) # Gets an element from the reduction, or o if we don't have # such an element. def get(idx, default): if idx < len(reduction) and reduction[idx] is not None: return reduction[idx] else: return default state = get(2, {}) if isinstance(state, dict): for k, v in six.iteritems(state): rv = visit(v, path + "." + k) if rv is not None: return rv else: rv = visit(state, path + ".__getstate__()") if rv is not None: return rv for i, oo in enumerate(get(3, [])): rv = visit(oo, "{0}[{1}]".format(path, i)) if rv is not None: return rv for i in get(4, []): if len(i) != 2: continue k, v = i rv = visit(v, "{0}[{1!r}]".format(path, k)) if rv is not None: return rv return None
def profile_rollback(): """ :doc: memory Profiles memory used by the rollback system. Writes (to memory.txt and stdout) the memory used by the rollback system. This tries to account for rollback memory used by various store variables, as well as by internal aspects of the rollback system. """ write("=" * 78) write("") write("Rollback profile at " + time.ctime() + ":") write("") # Profile live memory. seen = profile_memory_common(["store", "renpy.display"])[1] # Like seen, but for objects found in rollback. new_seen = {} log = list(renpy.game.log.log) log.reverse() roots = [] # Walk the log, finding new roots and rollback information. for rb in log: for store_name, store in six.iteritems(rb.stores): for var_name, o in six.iteritems(store): name = store_name + "." + var_name id_o = id(o) if (id_o not in seen) and (id_o not in new_seen): new_seen[id_o] = name roots.append((name, o)) for o, roll in rb.objects: id_o = id(o) name = "<unknown>" name = new_seen.get(id_o, name) name = seen.get(id_o, name) roots.append((name, roll)) roots.append(("<scene lists>", rb.context.scene_lists)) roots.append(("<context>", rb.context)) sizes = walk_memory(roots, seen)[0] usage = [(v, k) for (k, v) in six.iteritems(sizes)] usage.sort() write("Total Bytes".rjust(13) + " " + "Per Rollback".rjust(13)) write("-" * 13 + " " + "-" * 13 + " " + "-" * 50) for size, name in usage: if name.startswith("renpy"): continue if size: write("{:13,d} {:13,d} {}".format(size, size // len(log), name)) write("") write("{} Rollback objects exist.".format(len(log))) write("")
def visit(old_ido, o, path): ido = id(o) if old_ido is not None: edges.add((old_ido, ido, path)) if ido in o_repr_cache: return paths[ido] = path if isinstance(o, (int, float, type(None), types.ModuleType, type)): o_repr = repr(o) elif isinstance(o, (str, six.text_type)): if len(o) <= 80: o_repr = repr(o).encode("utf-8") else: o_repr = repr(o[:80] + "...").encode("utf-8") elif isinstance(o, (tuple, list)): o_repr = "<" + o.__class__.__name__ + ">" elif isinstance(o, dict): o_repr = "<" + o.__class__.__name__ + ">" elif isinstance(o, types.MethodType): o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__func__.__name__) elif isinstance(o, object): o_repr = "<{0}>".format(type(o).__name__) else: o_repr = "BAD TYPE <{0}>".format(type(o).__name__) o_repr_cache[ido] = o_repr if isinstance(o, (tuple, list)): for i, oo in enumerate(o): visit(ido, oo, "{0}[{1!r}]".format(path, i)) if isinstance(o, dict): for k, v in six.iteritems(o): visit(ido, v, "{0}[{1!r}]".format(path, k)) elif isinstance(o, types.MethodType): visit(ido, o.__self__, path + ".im_self") else: try: reduction = o.__reduce_ex__(2) except: reduction = [] # Gets an element from the reduction, or o if we don't have # such an element. def get(idx, default): if idx < len(reduction) and reduction[idx] is not None: return reduction[idx] else: return default state = get(2, {}) if isinstance(state, dict): for k, v in six.iteritems(state): visit(ido, v, path + "." + k) else: visit(ido, state, path + ".__getstate__()") for i, oo in enumerate(get(3, [])): visit(ido, oo, "{0}[{1}]".format(path, i)) for i in get(4, []): if len(i) != 2: continue k, v = i visit(ido, v, "{0}[{1!r}]".format(path, k))
def dump(error): """ Causes a JSON dump file to be written, if the user has requested it. `error` An error flag that is added to the written file. """ global completed_dump args = renpy.game.args if completed_dump: return completed_dump = True if not args.json_dump: return def filter(name, filename): # @ReservedAssignment """ Returns true if the name is included by the filter, or false if it is excluded. """ filename = filename.replace("\\", "/") if name.startswith("_") and not args.json_dump_private: if name.startswith("__") and name.endswith("__"): pass else: return False if not file_exists(filename): return False if filename.startswith("common/") or filename.startswith( "renpy/common/"): return args.json_dump_common if not filename.startswith("game/"): return False return True result = {} # Error flag. result["error"] = error # The size. result["size"] = [renpy.config.screen_width, renpy.config.screen_height] # The name and version. result["name"] = renpy.config.name result["version"] = renpy.config.version # The JSON object we return. location = {} result["location"] = location # Labels. label = location["label"] = {} for name, n in six.iteritems(renpy.game.script.namemap): filename = n.filename line = n.linenumber if not isinstance(name, six.string_types): continue if not filter(name, filename): continue label[name] = [filename, line] # Definitions. define = location["define"] = {} for name, filename, line in definitions: if not filter(name, filename): continue define[name] = [filename, line] # Screens. screen = location["screen"] = {} for name, filename, line in screens: if not filter(name, filename): continue screen[name] = [filename, line] # Transforms. transform = location["transform"] = {} for name, filename, line in transforms: if not filter(name, filename): continue transform[name] = [filename, line] # Code. def get_line(o): """ Returns the filename and the first line number of the class or function o. Returns None, None if unknown. For a class, this doesn't return the first line number of the class, but rather the line number of the first method in the class - hopefully. """ if inspect.isfunction(o): return inspect.getfile(o), o.__code__.co_firstlineno if inspect.ismethod(o): return get_line(o.__func__) return None, None code = location["callable"] = {} for modname, mod in sys.modules.items(): if mod is None: continue if modname == "store": prefix = "" elif modname.startswith("store."): prefix = modname[6:] + "." else: continue for name, o in mod.__dict__.items(): if inspect.isfunction(o): try: if inspect.getmodule(o) != mod: continue filename, line = get_line(o) if filename is None: continue if not filter(name, filename): continue code[prefix + name] = [filename, line] except: continue if inspect.isclass(o): for methname, method in o.__dict__.iteritems(): try: if inspect.getmodule(method) != mod: continue filename, line = get_line(method) if filename is None: continue if not filter(name, filename): continue if not filter(methname, filename): continue code[prefix + name + "." + methname] = [filename, line] except: continue # Add the build info from 00build.rpy, if it's available. try: result["build"] = renpy.store.build.dump() # @UndefinedVariable except: pass if args.json_dump != "-": new = args.json_dump + ".new" with open(new, "w") as f: json.dump(result, f) if os.path.exists(args.json_dump): os.unlink(args.json_dump) os.rename(new, args.json_dump) else: json.dump(result, sys.stdout, indent=2)
def visit(o, path): ido = id(o) if ido in o_repr_cache: f.write("{0: 7d} {1} = alias {2}\n".format(0, path, o_repr_cache[ido])) return 0 if isinstance(o, (int, float, type(None), types.ModuleType, type)): o_repr = repr(o) elif isinstance(o, (str, bytes, six.text_type)): if len(o) <= 80: o_repr = repr(o).encode("utf-8") else: o_repr = repr(o[:80] + "...").encode("utf-8") elif isinstance(o, (tuple, list)): o_repr = "<" + o.__class__.__name__ + ">" elif isinstance(o, dict): o_repr = "<" + o.__class__.__name__ + ">" elif isinstance(o, types.MethodType): o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__func__.__name__) elif isinstance(o, object): o_repr = "<{0}>".format(type(o).__name__) else: o_repr = "BAD TYPE <{0}>".format(type(o).__name__) o_repr_cache[ido] = o_repr if isinstance(o, (int, float, type(None), types.ModuleType, type)): size = 1 elif isinstance(o, (str, six.text_type)): size = len(o) // 40 + 1 elif isinstance(o, (tuple, list)): size = 1 for i, oo in enumerate(o): size += 1 size += visit(oo, "{0}[{1!r}]".format(path, i)) elif isinstance(o, dict): size = 2 for k, v in six.iteritems(o): size += 2 size += visit(v, "{0}[{1!r}]".format(path, k)) elif isinstance(o, types.MethodType): size = 1 + visit(o.__self__, path + ".im_self") else: try: reduction = o.__reduce_ex__(2) except: reduction = [] o_repr = "BAD REDUCTION " + o_repr # Gets an element from the reduction, or o if we don't have # such an element. def get(idx, default): if idx < len(reduction) and reduction[idx] is not None: return reduction[idx] else: return default # An estimate of the size of the object, in arbitrary units. (These units are about 20-25 bytes on # my computer.) size = 1 state = get(2, {}) if isinstance(state, dict): for k, v in six.iteritems(state): size += 2 size += visit(v, path + "." + k) else: size += visit(state, path + ".__getstate__()") for i, oo in enumerate(get(3, [])): size += 1 size += visit(oo, "{0}[{1}]".format(path, i)) for i in get(4, []): if len(i) != 2: continue k, v = i size += 2 size += visit(v, "{0}[{1!r}]".format(path, k)) f.write("{0: 7d} {1} = {2}\n".format(size, path, o_repr_cache[ido])) return size
def bootstrap(renpy_base): global renpy # W0602 import renpy.log # @UnusedImport # Remove a legacy environment setting. if os.environ.get(b"SDL_VIDEODRIVER", "") == "windib": del os.environ[b"SDL_VIDEODRIVER"] renpy_base = six.text_type(renpy_base, FSENCODING, "replace") # If environment.txt exists, load it into the os.environ dictionary. if os.path.exists(renpy_base + "/environment.txt"): evars = {} with open(renpy_base + "/environment.txt", "rb") as f: code = compile(f.read(), renpy_base + "/environment.txt", 'exec') exec(code, evars) for k, v in six.iteritems(evars): if k not in os.environ: os.environ[k] = str(v) # Also look for it in an alternate path (the path that contains the # .app file.), if on a mac. alt_path = os.path.abspath("renpy_base") if ".app" in alt_path: alt_path = alt_path[:alt_path.find(".app") + 4] if os.path.exists(alt_path + "/environment.txt"): evars = {} with open(alt_path + "/environment.txt", "rb") as f: code = compile(f.read(), alt_path + "/environment.txt", 'exec') exec(code, evars) for k, v in six.iteritems(evars): if k not in os.environ: os.environ[k] = str(v) # Get a working name for the game. name = os.path.basename(sys.argv[0]) if name.find(".") != -1: name = name[:name.find(".")] # Parse the arguments. import renpy.arguments args = renpy.arguments.bootstrap() if args.trace: enable_trace(args.trace) if args.basedir: basedir = os.path.abspath(args.basedir).decode(FSENCODING) else: basedir = renpy_base if not os.path.exists(basedir): sys.stderr.write("Base directory %r does not exist. Giving up.\n" % (basedir, )) sys.exit(1) gamedirs = [name] game_name = name while game_name: prefix = game_name[0] game_name = game_name[1:] if prefix == ' ' or prefix == '_': gamedirs.append(game_name) gamedirs.extend(['game', 'data', 'launcher/game']) for i in gamedirs: if i == "renpy": continue gamedir = basedir + "/" + i if os.path.isdir(gamedir): break else: gamedir = basedir sys.path.insert(0, basedir) if renpy.macintosh: # If we're on a mac, install our own os.start. os.startfile = mac_start # Are we starting from inside a mac app resources directory? if basedir.endswith("Contents/Resources/autorun"): renpy.macapp = True # Check that we have installed pygame properly. This also deals with # weird cases on Windows and Linux where we can't import modules. (On # windows ";" is a directory separator in PATH, so if it's in a parent # directory, we won't get the libraries in the PATH, and hence pygame # won't import.) try: import pygame_sdl2 if not ("pygame" in sys.modules): pygame_sdl2.import_as_pygame() except: print("""\ Could not import pygame_sdl2. Please ensure that this program has been built and unpacked properly. Also, make sure that the directories containing this program do not contain : or ; in their names. You may be using a system install of python. Please run {0}.sh, {0}.exe, or {0}.app instead. """.format(name), file=sys.stderr) raise # If we're not given a command, show the presplash. if args.command == "run" and not renpy.mobile: import renpy.display.presplash # @Reimport renpy.display.presplash.start(basedir, gamedir) # Ditto for the Ren'Py module. try: import _renpy _renpy except: print("""\ Could not import _renpy. Please ensure that this program has been built and unpacked properly. You may be using a system install of python. Please run {0}.sh, {0}.exe, or {0}.app instead. """.format(name), file=sys.stderr) raise # Load up all of Ren'Py, in the right order. import renpy # @Reimport renpy.import_all() renpy.loader.init_importer() exit_status = None try: while exit_status is None: exit_status = 1 try: renpy.game.args = args renpy.config.renpy_base = renpy_base renpy.config.basedir = basedir renpy.config.gamedir = gamedir renpy.config.args = [] if renpy.android: renpy.config.logdir = os.environ['ANDROID_PUBLIC'] else: renpy.config.logdir = basedir if not os.path.exists(renpy.config.logdir): os.makedirs(renpy.config.logdir, 0o777) renpy.main.main() exit_status = 0 except KeyboardInterrupt: raise except renpy.game.UtterRestartException: # On an UtterRestart, reload Ren'Py. renpy.reload_all() exit_status = None except renpy.game.QuitException as e: exit_status = e.status if e.relaunch: if hasattr(sys, "renpy_executable"): subprocess.Popen([sys.renpy_executable] + sys.argv[1:]) else: subprocess.Popen([sys.executable, "-EO"] + sys.argv) except renpy.game.ParseErrorException: pass except Exception as e: renpy.error.report_exception(e) pass sys.exit(exit_status) finally: if "RENPY_SHUTDOWN_TRACE" in os.environ: enable_trace(int(os.environ["RENPY_SHUTDOWN_TRACE"])) renpy.display.im.cache.quit() if renpy.display.draw: renpy.display.draw.quit() renpy.audio.audio.quit() # Prevent subprocess from throwing errors while trying to run it's # __del__ method during shutdown. if not renpy.emscripten: subprocess.Popen.__del__ = popen_del