1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22# This file contains code that handles the execution of python code
23# contained within the script file. It also handles rolling back the
24# game state to some time in the past.
25
26from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
27from renpy.compat import *
28
29# Import the python ast module, not ours.
30import ast
31
32# Import the future module itself.
33import __future__
34
35import marshal
36import random
37import weakref
38import re
39import sys
40import time
41import io
42import types
43import copyreg
44import functools
45
46import renpy.audio
47
48# A set of flags that indicate dict should run in future-compatible mode.
49FUTURE_FLAGS = (__future__.CO_FUTURE_DIVISION | __future__.CO_FUTURE_WITH_STATEMENT)
50
51##############################################################################
52# Monkeypatch copy_reg to work around a change in the class that RevertableSet
53# is based on.
54
55
56def _reconstructor(cls, base, state):
57    if (cls is RevertableSet) and (base is object):
58        base = set
59        state = [ ]
60
61    if base is object:
62        obj = object.__new__(cls)
63    else:
64        obj = base.__new__(cls, state)
65        if base.__init__ != object.__init__:
66            base.__init__(obj, state)
67
68    return obj
69
70
71copyreg._reconstructor = _reconstructor
72
73##############################################################################
74# Code that implements the store.
75
76# Deleted is a singleton object that's used to represent an object that has
77# been deleted from the store.
78
79
80class StoreDeleted(object):
81
82    def __reduce__(self):
83        if PY2:
84            return b"deleted"
85        else:
86            return "deleted"
87
88
89deleted = StoreDeleted()
90
91
92class StoreModule(object):
93    """
94    This class represents one of the modules containing the store of data.
95    """
96
97    # Set our dict to be the StoreDict. Then proxy over setattr and delattr,
98    # since Python won't call them by default.
99
100    def __reduce__(self):
101        return (get_store_module, (self.__name__,))
102
103    def __init__(self, d):
104        object.__setattr__(self, "__dict__", d)
105
106    def __setattr__(self, key, value):
107        self.__dict__[key] = value
108
109    def __delattr__(self, key):
110        del self.__dict__[key]
111
112# Used to unpickle a store module.
113
114
115def get_store_module(name):
116    return sys.modules[name]
117
118
119from renpy.pydict import DictItems, find_changes
120
121EMPTY_DICT = { }
122EMPTY_SET = set()
123
124
125class StoreDict(dict):
126    """
127    This class represents the dictionary of a store module. It logs
128    sets and deletes.
129    """
130
131    def __reduce__(self):
132        raise Exception("Cannot pickle a reference to a store dictionary.")
133
134    def __init__(self):
135
136        # The value of this dictionary at the start of the current
137        # rollback period (when begin() was last called).
138        self.old = DictItems(self)
139
140        # The set of variables in this StoreDict that changed since the
141        # end of the init phase.
142        self.ever_been_changed = set()
143
144    def reset(self):
145        """
146        Called to reset this to its initial conditions.
147        """
148
149        self.ever_been_changed = set()
150        self.clear()
151        self.old = DictItems(self)
152
153    def begin(self):
154        """
155        Called to mark the start of a rollback period.
156        """
157
158        self.old = DictItems(self)
159
160    def get_changes(self, cycle):
161        """
162        For every key that has changed since begin() was called, returns a
163        dictionary mapping the key to its value when begin was called, or
164        deleted if it did not exist when begin was called.
165
166        As a side-effect, updates self.ever_been_changed, and returns the
167        changes to ever_been_changed as well.
168
169        `cycle`
170            If true, this cycles the old changes to the new changes. If
171            False, does not.
172        """
173
174        new = DictItems(self)
175        rv = find_changes(self.old, new, deleted)
176
177        if cycle:
178            self.old = new
179
180        if rv is None:
181            return EMPTY_DICT, EMPTY_SET
182
183        delta_ebc = set()
184
185        if cycle:
186
187            for k in rv:
188                if k not in self.ever_been_changed:
189                    self.ever_been_changed.add(k)
190                    delta_ebc.add(k)
191
192        return rv, delta_ebc
193
194
195def begin_stores():
196    """
197    Calls .begin on every store dict.
198    """
199
200    for sd in store_dicts.values():
201        sd.begin()
202
203
204# A map from the name of a store dict to the corresponding StoreDict object.
205# This isn't reset during a reload, so store objects stay the same in modules.
206store_dicts = { }
207
208# Same, for module objects.
209store_modules = { }
210
211# The store dicts that have been cleared and initialized during the current
212# run.
213initialized_store_dicts = set()
214
215
216def create_store(name):
217    """
218    Creates the store with `name`.
219    """
220
221    parent, _, var = name.rpartition('.')
222
223    if parent:
224        create_store(parent)
225
226    name = str(name)
227
228    if name in initialized_store_dicts:
229        return
230
231    initialized_store_dicts.add(name)
232
233    # Create the dict.
234    d = store_dicts.setdefault(name, StoreDict())
235    d.reset()
236
237    pyname = pystr(name)
238
239    # Set the name.
240    d["__name__"] = pyname
241    d["__package__"] = pyname
242
243    # Set up the default contents of the store.
244    eval("1", d)
245
246    for k, v in renpy.minstore.__dict__.items():
247        if k not in d:
248            d[k] = v
249
250    # Create or reuse the corresponding module.
251    if name in store_modules:
252        sys.modules[pyname] = store_modules[name]
253    else:
254        store_modules[name] = sys.modules[pyname] = StoreModule(d)
255
256    if parent:
257        store_dicts[parent][var] = sys.modules[pyname]
258
259
260class StoreBackup():
261    """
262    This creates a copy of the current store, as it was at the start of
263    the current statement.
264    """
265
266    def __init__(self):
267
268        # The contents of the store for each store.
269        self.store = { }
270
271        # The contents of old for each store.
272        self.old = { }
273
274        # The contents of ever_been_changed for each store.
275        self.ever_been_changed = { }
276
277        for k in store_dicts:
278            self.backup_one(k)
279
280    def backup_one(self, name):
281
282        d = store_dicts[name]
283
284        self.store[name] = dict(d)
285        self.old[name] = d.old.as_dict()
286        self.ever_been_changed[name] = set(d.ever_been_changed)
287
288    def restore_one(self, name):
289        sd = store_dicts[name]
290
291        sd.clear()
292        sd.update(self.store[name])
293
294        sd.old = DictItems(self.old[name])
295
296        sd.ever_been_changed.clear()
297        sd.ever_been_changed.update(self.ever_been_changed[name])
298
299    def restore(self):
300
301        for k in store_dicts:
302            self.restore_one(k)
303
304
305clean_store_backup = None
306
307
308def make_clean_stores():
309    """
310    Copy the clean stores.
311    """
312
313    global clean_store_backup
314
315    for _k, v in store_dicts.items():
316
317        v.ever_been_changed.clear()
318        v.begin()
319
320    clean_store_backup = StoreBackup()
321
322
323def clean_stores():
324    """
325    Revert the store to the clean copy.
326    """
327
328    clean_store_backup.restore()
329
330
331def clean_store(name):
332    """
333    Reverts the named store to its clean copy.
334    """
335
336    if not name.startswith("store."):
337        name = "store." + name
338
339    clean_store_backup.restore_one(name)
340
341
342def reset_store_changes(name):
343
344    if not name.startswith("store."):
345        name = "store." + name
346
347    sd = store_dicts[name]
348    sd.begin()
349
350# Code that computes reachable objects, which is used to filter
351# the rollback list before rollback or serialization.
352
353
354class NoRollback(object):
355    """
356    :doc: norollback class
357
358    Instances of classes inheriting from this class do not participate in
359    rollback. Objects reachable through an instance of a NoRollback class
360    only participate in rollback if they are reachable through other paths.
361    """
362
363    pass
364
365# parents = [ ]
366
367
368def reached(obj, reachable, wait):
369    """
370    @param obj: The object that was reached.
371
372    `reachable`
373        A map from id(obj) to int. The int is 1 if the object was reached
374        normally, and 0 if it was reached, but inherits from NoRollback.
375    """
376
377    if wait:
378        wait()
379
380    idobj = id(obj)
381
382    if idobj in reachable:
383        return
384
385    if isinstance(obj, (NoRollback, io.IOBase)): # @UndefinedVariable
386        reachable[idobj] = 0
387        return
388
389    reachable[idobj] = 1
390
391    # Since the store module is the roots, there's no need to
392    # look into it.
393    if isinstance(obj, StoreModule):
394        return
395
396    # parents.append(obj)
397
398    try:
399        # Treat as fields, indexed by strings.
400        for v in vars(obj).values():
401            reached(v, reachable, wait)
402    except:
403        pass
404
405    try:
406        # Treat as iterable
407        if not isinstance(obj, basestring):
408            for v in obj.__iter__():
409                reached(v, reachable, wait)
410    except:
411        pass
412
413    try:
414        # Treat as dict.
415        for v in obj.values():
416            reached(v, reachable, wait)
417    except:
418        pass
419
420    # parents.pop()
421
422
423def reached_vars(store, reachable, wait):
424    """
425    Marks everything reachable from the variables in the store
426    or from the context info objects as reachable.
427
428    @param store: A map from variable name to variable value.
429    @param reachable: A dictionary mapping reached object ids to
430    the path by which the object was reached.
431    """
432
433    for v in store.values():
434        reached(v, reachable, wait)
435
436    for c in renpy.game.contexts:
437        reached(c.info, reachable, wait)
438        reached(c.music, reachable, wait)
439        for d in c.dynamic_stack:
440            for v in d.values():
441                reached(v, reachable, wait)
442
443# Code that replaces literals will calls to magic constructors.
444
445
446def b(s):
447    if PY2:
448        return s.encode("utf-8")
449    else:
450        return s
451
452
453class WrapNode(ast.NodeTransformer):
454
455    def visit_ClassDef(self, n):
456        n = self.generic_visit(n)
457
458        if not n.bases:
459            n.bases.append(ast.Name(id=b("object"), ctx=ast.Load()))
460
461        return n
462
463    def visit_SetComp(self, n):
464        return ast.Call(
465            func=ast.Name(
466                id=b("__renpy__set__"),
467                ctx=ast.Load()
468                ),
469            args=[ self.generic_visit(n) ],
470            keywords=[ ],
471            starargs=None,
472            kwargs=None)
473
474    def visit_Set(self, n):
475
476        return ast.Call(
477            func=ast.Name(
478                id=b("__renpy__set__"),
479                ctx=ast.Load()
480                ),
481            args=[ self.generic_visit(n) ],
482            keywords=[ ],
483            starargs=None,
484            kwargs=None)
485
486    def visit_ListComp(self, n):
487        return ast.Call(
488            func=ast.Name(
489                id=b("__renpy__list__"),
490                ctx=ast.Load()
491                ),
492            args=[ self.generic_visit(n) ],
493            keywords=[ ],
494            starargs=None,
495            kwargs=None)
496
497    def visit_List(self, n):
498        if not isinstance(n.ctx, ast.Load):
499            return self.generic_visit(n)
500
501        return ast.Call(
502            func=ast.Name(
503                id=b("__renpy__list__"),
504                ctx=ast.Load()
505                ),
506            args=[ self.generic_visit(n) ],
507            keywords=[ ],
508            starargs=None,
509            kwargs=None)
510
511    def visit_DictComp(self, n):
512        return ast.Call(
513            func=ast.Name(
514                id=b("__renpy__dict__"),
515                ctx=ast.Load()
516                ),
517            args=[ self.generic_visit(n) ],
518            keywords=[ ],
519            starargs=None,
520            kwargs=None)
521
522    def visit_Dict(self, n):
523
524        return ast.Call(
525            func=ast.Name(
526                id=b("__renpy__dict__"),
527                ctx=ast.Load()
528                ),
529            args=[ self.generic_visit(n) ],
530            keywords=[ ],
531            starargs=None,
532            kwargs=None)
533
534
535wrap_node = WrapNode()
536
537
538def wrap_hide(tree):
539    """
540    Wraps code inside a python hide or python early hide block inside a
541    function, so it gets its own scope that works the way Python expects
542    it to.
543    """
544
545    hide = ast.parse("""\
546def _execute_python_hide(): pass;
547_execute_python_hide()
548""")
549
550    for i in ast.walk(hide):
551        ast.copy_location(i, hide.body[0])
552
553    hide.body[0].body = tree.body
554    tree.body = hide.body
555
556
557unicode_re = re.compile(r'[\u0080-\uffff]')
558
559
560def unicode_sub(m):
561    """
562    If the string s contains a unicode character, make it into a
563    unicode string.
564    """
565
566    s = m.group(0)
567
568    if not unicode_re.search(s):
569        return s
570
571    prefix = m.group(1)
572    sep = m.group(2)
573    body = m.group(3)
574
575    if "u" not in prefix and "U" not in prefix:
576        prefix = 'u' + prefix
577
578    rv = prefix + sep + body + sep
579
580    return rv
581
582
583string_re = re.compile(r'([uU]?[rR]?)("""|"|\'\'\'|\')((\\.|.)*?)\2')
584
585
586def escape_unicode(s):
587    if unicode_re.search(s):
588        s = string_re.sub(unicode_sub, s)
589
590    return s
591
592
593# Flags used by py_compile.
594old_compile_flags = (__future__.nested_scopes.compiler_flag
595                      | __future__.with_statement.compiler_flag
596                      )
597
598new_compile_flags = (old_compile_flags
599                      | __future__.absolute_import.compiler_flag
600                      | __future__.print_function.compiler_flag
601                      | __future__.unicode_literals.compiler_flag
602                      )
603
604py3_compile_flags = (new_compile_flags |
605                      __future__.division.compiler_flag)
606
607# The set of files that should be compiled under Python 2 with Python 3
608# semantics.
609py3_files = set()
610
611# A cache for the results of py_compile.
612py_compile_cache = { }
613
614# An old version of the same, that's preserved across reloads.
615old_py_compile_cache = { }
616
617
618# Duplicated from ast.py to prevent a gc cycle.
619def fix_missing_locations(node, lineno, col_offset):
620    if 'lineno' in node._attributes:
621        if not hasattr(node, 'lineno'):
622            node.lineno = lineno
623        else:
624            lineno = node.lineno
625    if 'col_offset' in node._attributes:
626        if not hasattr(node, 'col_offset'):
627            node.col_offset = col_offset
628        else:
629            col_offset = node.col_offset
630    for child in ast.iter_child_nodes(node):
631        fix_missing_locations(child, lineno, col_offset)
632
633
634def quote_eval(s):
635    """
636    Quotes a string for `eval`. This is necessary when it's in certain places,
637    like as part of an argument string. We need to stick \ at the end of lines
638    that don't have it already, and that aren't in triple-quoted strings.
639    """
640
641    # No newlines! No problem.
642    if "\n" not in s:
643        return s
644
645    # Characters being added to the string.
646    rv = [ ]
647
648    # Pad out the string, so we don't have to deal with quotes at the end.
649    s += "\0\0"
650
651    len_s = len(s)
652
653    # The index into the string.
654    i = 0
655
656    # Special characters, that aren't just copied into the string.
657    special = "\0\\'\"\n"
658
659    # The string currently being processed.
660    string = None
661
662    while i < len_s:
663
664        c = s[i]
665
666        # Non-special characters.
667        if c not in special:
668            start = i
669
670            while True:
671                i += 1
672                if s[i] in special:
673                    break
674
675            rv.append(s[start:i])
676            continue
677
678        # Null.
679        if c == '\0':
680            rv.append(c)
681            i += 1
682            continue
683
684        # Any escaped character passes.
685        if c == '\\':
686            rv.append(s[i:i + 2])
687            i += 2
688            continue
689
690        # String delimiters.
691        if c in '\'"':
692
693            if ((string is None) or (len(string) == 3)) and (s[i + 1] == c) and (s[i + 2] == c):
694                delim = c + c + c
695            else:
696                delim = c
697
698            if (string is not None) and (delim == string):
699                string = None
700            elif string is None:
701                string = delim
702
703            rv.append(delim)
704            i += len(delim)
705
706            continue
707
708        # Newline.
709        if c == "\n":
710            if string is None:
711                rv.append('\\')
712
713            rv.append("\n")
714            i += 1
715            continue
716
717        raise Exception("Unknown character %r (can't happen)".format(c))
718
719    # Since the last 2 characters are \0, those characters need to be stripped.
720    return "".join(rv[:-2])
721
722
723def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=True):
724    """
725    Compiles the given source code using the supplied codegenerator.
726    Lists, List Comprehensions, and Dictionaries are wrapped when
727    appropriate.
728
729    `source`
730        The source code, as a either a string, pyexpr, or ast module
731        node.
732
733    `mode`
734        One of "exec" or "eval".
735
736    `filename`
737        The filename the source comes from. If a pyexpr is given, the
738        filename embedded in the pyexpr is used.
739
740    `lineno`
741        The line number of the first line of source code. If a pyexpr is
742        given, the filename embedded in the pyexpr is used.
743
744    `ast_node`
745        Rather than returning compiled bytecode, returns the AST object
746        that would be used.
747    """
748
749    if ast_node:
750        cache = False
751
752    if isinstance(source, ast.Module):
753        return compile(source, filename, mode)
754
755    if isinstance(source, renpy.ast.PyExpr):
756        filename = source.filename
757        lineno = source.linenumber
758
759    if cache:
760        key = (lineno, filename, str(source), mode, renpy.script.MAGIC)
761
762        rv = py_compile_cache.get(key, None)
763        if rv is not None:
764            return rv
765
766        rv = old_py_compile_cache.get(key, None)
767        if rv is not None:
768            py_compile_cache[key] = rv
769            return rv
770
771        bytecode = renpy.game.script.bytecode_oldcache.get(key, None)
772        if bytecode is not None:
773
774            renpy.game.script.bytecode_newcache[key] = bytecode
775            rv = marshal.loads(bytecode)
776            py_compile_cache[key] = rv
777            return rv
778
779    source = str(source)
780    source = source.replace("\r", "")
781
782    if mode == "eval":
783        source = quote_eval(source)
784
785    try:
786        line_offset = lineno - 1
787
788        if mode == "hide":
789            py_mode = "exec"
790        else:
791            py_mode = mode
792
793        if filename in py3_files:
794
795            flags = py3_compile_flags
796            tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
797
798        else:
799
800            try:
801                flags = new_compile_flags
802                tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
803            except:
804                flags = old_compile_flags
805                source = escape_unicode(source)
806                tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
807
808        tree = wrap_node.visit(tree)
809
810        if mode == "hide":
811            wrap_hide(tree)
812
813        fix_missing_locations(tree, 1, 0)
814        ast.increment_lineno(tree, lineno - 1)
815
816        line_offset = 0
817
818        if ast_node:
819            return tree.body
820
821        rv = compile(tree, filename, py_mode, flags, 1)
822
823        if cache:
824            py_compile_cache[key] = rv
825            renpy.game.script.bytecode_newcache[key] = marshal.dumps(rv)
826            renpy.game.script.bytecode_dirty = True
827
828        return rv
829
830    except SyntaxError as e:
831
832        if e.lineno is not None:
833            e.lineno += line_offset
834
835        raise e
836
837
838def py_compile_exec_bytecode(source, **kwargs):
839    code = py_compile(source, 'exec', cache=False, **kwargs)
840    return marshal.dumps(code)
841
842
843def py_compile_hide_bytecode(source, **kwargs):
844    code = py_compile(source, 'hide', cache=False, **kwargs)
845    return marshal.dumps(code)
846
847
848def py_compile_eval_bytecode(source, **kwargs):
849    source = source.strip()
850    code = py_compile(source, 'eval', cache=False, **kwargs)
851    return marshal.dumps(code)
852
853# Classes that are exported in place of the normal list, dict, and
854# object.
855
856
857# This is set to True whenever a mutation occurs. The save code uses
858# this to check to see if a background-save is valid.
859mutate_flag = True
860
861
862def mutator(method):
863
864    @functools.wraps(method, ("__name__", "__doc__"), ())
865    def do_mutation(self, *args, **kwargs):
866
867        global mutate_flag
868
869        mutated = renpy.game.log.mutated # @UndefinedVariable
870
871        if id(self) not in mutated:
872            mutated[id(self)] = (weakref.ref(self), self._clean())
873            mutate_flag = True
874
875        return method(self, *args, **kwargs)
876
877    return do_mutation
878
879
880class CompressedList(object):
881    """
882    Compresses the changes in a queue-like list. What this does is to try
883    to find a central sub-list for which has objects in both lists. It
884    stores the location of that in the new list, and then elements before
885    and after in the sub-list.
886
887    This only really works if the objects in the list are unique, but the
888    results are efficient even if this doesn't work.
889    """
890
891    def __init__(self, old, new):
892
893        # Pick out a pivot element near the center of the list.
894        new_center = (len(new) - 1) // 2
895        new_pivot = new[new_center]
896
897        # Find an element in the old list corresponding to the pivot.
898        old_half = (len(old) - 1) // 2
899
900        for i in range(0, old_half + 1):
901
902            if old[old_half - i] is new_pivot:
903                old_center = old_half - i
904                break
905
906            if old[old_half + i] is new_pivot:
907                old_center = old_half + i
908                break
909        else:
910            # If we couldn't, give up.
911            self.pre = old
912            self.start = 0
913            self.end = 0
914            self.post = [ ]
915
916            return
917
918        # Figure out the position of the overlap in the center of the two lists.
919        new_start = new_center
920        new_end = new_center + 1
921
922        old_start = old_center
923        old_end = old_center + 1
924
925        len_new = len(new)
926        len_old = len(old)
927
928        while new_start and old_start and (new[new_start - 1] is old[old_start - 1]):
929            new_start -= 1
930            old_start -= 1
931
932        while (new_end < len_new) and (old_end < len_old) and (new[new_end] is old[old_end]):
933            new_end += 1
934            old_end += 1
935
936        # Now that we have this, we can put together the object.
937        self.pre = list.__getitem__(old, slice(0, old_start))
938        self.start = new_start
939        self.end = new_end
940        self.post = list.__getitem__(old, slice(old_end, len_old))
941
942    def decompress(self, new):
943        return self.pre + new[self.start:self.end] + self.post
944
945    def __repr__(self):
946        return "<CompressedList {} [{}:{}] {}>".format(
947            self.pre,
948            self.start,
949            self.end,
950            self.post)
951
952
953class RevertableList(list):
954
955    def __init__(self, *args):
956        log = renpy.game.log
957
958        if log is not None:
959            log.mutated[id(self)] = None
960
961        list.__init__(self, *args)
962
963    __delitem__ = mutator(list.__delitem__)
964    if PY2:
965        __delslice__ = mutator(list.__delslice__)
966    __setitem__ = mutator(list.__setitem__)
967    if PY2:
968        __setslice__ = mutator(list.__setslice__)
969    __iadd__ = mutator(list.__iadd__)
970    __imul__ = mutator(list.__imul__)
971    append = mutator(list.append)
972    extend = mutator(list.extend)
973    insert = mutator(list.insert)
974    pop = mutator(list.pop)
975    remove = mutator(list.remove)
976    reverse = mutator(list.reverse)
977    sort = mutator(list.sort)
978
979    def wrapper(method): # E0213 @NoSelf
980
981        def newmethod(*args, **kwargs):
982            l = method(*args, **kwargs)
983            return RevertableList(l)
984
985        return newmethod
986
987    __add__ = wrapper(list.__add__)
988    if PY2:
989        __getslice__ = wrapper(list.__getslice__)
990
991    def __getitem__(self, index):
992        rv = list.__getitem__(self, index)
993
994        if isinstance(index, slice):
995            return RevertableList(rv)
996        else:
997            return rv
998
999    def __mul__(self, other):
1000        if not isinstance(other, int):
1001            raise TypeError("can't multiply sequence by non-int of type '{}'.".format(type(other).__name__))
1002
1003        return RevertableList(list.__mul__(self, other))
1004
1005    __rmul__ = __mul__
1006
1007    def copy(self):
1008        return self[:]
1009
1010    def clear(self):
1011        self[:] = []
1012
1013    def _clean(self):
1014        """
1015        Gets a clean copy of this object before any mutation occurs.
1016        """
1017
1018        return self[:]
1019
1020    def _compress(self, clean):
1021        """
1022        Takes a clean copy of this object, compresses it, and returns compressed
1023        information that can be passed to rollback.
1024        """
1025
1026        if not self or not clean:
1027            return clean
1028
1029        if renpy.config.list_compression_length is None:
1030            return clean
1031
1032        if len(self) < renpy.config.list_compression_length or len(clean) < renpy.config.list_compression_length:
1033            return clean
1034
1035        return CompressedList(clean, self)
1036
1037    def _rollback(self, compressed):
1038        """
1039        Rolls this object back, using the information created by _compress.
1040
1041        Since compressed can come from a save file, this method also has to
1042        recognize and deal with old data.
1043        """
1044
1045        if isinstance(compressed, CompressedList):
1046            self[:] = compressed.decompress(self)
1047        else:
1048            self[:] = compressed
1049
1050
1051def revertable_range(*args):
1052    return RevertableList(range(*args))
1053
1054
1055def revertable_sorted(*args, **kwargs):
1056    return RevertableList(sorted(*args, **kwargs))
1057
1058
1059class RevertableDict(dict):
1060
1061    def __init__(self, *args, **kwargs):
1062        log = renpy.game.log
1063
1064        if log is not None:
1065            log.mutated[id(self)] = None
1066
1067        dict.__init__(self, *args, **kwargs)
1068
1069    __delitem__ = mutator(dict.__delitem__)
1070    __setitem__ = mutator(dict.__setitem__)
1071    clear = mutator(dict.clear)
1072    pop = mutator(dict.pop)
1073    popitem = mutator(dict.popitem)
1074    setdefault = mutator(dict.setdefault)
1075    update = mutator(dict.update)
1076
1077    if PY2:
1078
1079        def keys(self):
1080            rv = dict.keys(self)
1081
1082            if (sys._getframe(1).f_code.co_flags & FUTURE_FLAGS) != FUTURE_FLAGS:
1083                rv = RevertableList(rv)
1084
1085            return rv
1086
1087        def values(self):
1088            rv = dict.values(self)
1089
1090            if (sys._getframe(1).f_code.co_flags & FUTURE_FLAGS) != FUTURE_FLAGS:
1091                rv = RevertableList(rv)
1092
1093            return rv
1094
1095        def items(self):
1096            rv = dict.items(self)
1097
1098            if (sys._getframe(1).f_code.co_flags & FUTURE_FLAGS) != FUTURE_FLAGS:
1099                rv = RevertableList(rv)
1100
1101            return rv
1102
1103    def copy(self):
1104        rv = RevertableDict()
1105        rv.update(self)
1106        return rv
1107
1108    def _clean(self):
1109        return list(self.items())
1110
1111    def _compress(self, clean):
1112        return clean
1113
1114    def _rollback(self, compressed):
1115        self.clear()
1116
1117        for k, v in compressed:
1118            self[k] = v
1119
1120
1121class RevertableSet(set):
1122
1123    def __setstate__(self, state):
1124        if isinstance(state, tuple):
1125            self.update(state[0].keys())
1126        else:
1127            self.update(state)
1128
1129    def __getstate__(self):
1130        rv = ({ i : True for i in self},)
1131        return rv
1132
1133    # Required to ensure that getstate and setstate are called.
1134    __reduce__ = object.__reduce__
1135    __reduce_ex__ = object.__reduce_ex__
1136
1137    def __init__(self, *args):
1138        log = renpy.game.log
1139
1140        if log is not None:
1141            log.mutated[id(self)] = None
1142
1143        set.__init__(self, *args)
1144
1145    __iand__ = mutator(set.__iand__)
1146    __ior__ = mutator(set.__ior__)
1147    __isub__ = mutator(set.__isub__)
1148    __ixor__ = mutator(set.__ixor__)
1149    add = mutator(set.add)
1150    clear = mutator(set.clear)
1151    difference_update = mutator(set.difference_update)
1152    discard = mutator(set.discard)
1153    intersection_update = mutator(set.intersection_update)
1154    pop = mutator(set.pop)
1155    remove = mutator(set.remove)
1156    symmetric_difference_update = mutator(set.symmetric_difference_update)
1157    union_update = mutator(set.update)
1158    update = mutator(set.update)
1159
1160    def wrapper(method): # @NoSelf
1161
1162        def newmethod(*args, **kwargs):
1163            rv = method(*args, **kwargs)
1164            if isinstance(rv, (set, frozenset)):
1165                return RevertableSet(rv)
1166            else:
1167                return rv
1168
1169        return newmethod
1170
1171    __and__ = wrapper(set.__and__)
1172    __sub__ = wrapper(set.__sub__)
1173    __xor__ = wrapper(set.__xor__)
1174    __or__ = wrapper(set.__or__)
1175    copy = wrapper(set.copy)
1176    difference = wrapper(set.difference)
1177    intersection = wrapper(set.intersection)
1178    symmetric_difference = wrapper(set.symmetric_difference)
1179    union = wrapper(set.union)
1180
1181    del wrapper
1182
1183    def _clean(self):
1184        return list(self)
1185
1186    def _compress(self, clean):
1187        return clean
1188
1189    def _rollback(self, compressed):
1190        set.clear(self)
1191        set.update(self, compressed)
1192
1193
1194class RevertableObject(object):
1195
1196    def __new__(cls, *args, **kwargs):
1197        self = super(RevertableObject, cls).__new__(cls)
1198
1199        log = renpy.game.log
1200        if log is not None:
1201            log.mutated[id(self)] = None
1202
1203        return self
1204
1205    def __init__(self, *args, **kwargs):
1206        if (args or kwargs) and renpy.config.developer:
1207            raise TypeError("object() takes no parameters.")
1208
1209    def __setattr__(self, attr, value):
1210        object.__setattr__(self, attr, value)
1211
1212    def __delattr__(self, attr):
1213        object.__delattr__(self, attr)
1214
1215    __setattr__ = mutator(__setattr__)
1216    __delattr__ = mutator(__delattr__)
1217
1218    def _clean(self):
1219        return self.__dict__.copy()
1220
1221    def _compress(self, clean):
1222        return clean
1223
1224    def _rollback(self, compressed):
1225        self.__dict__.clear()
1226        self.__dict__.update(compressed)
1227
1228
1229class AlwaysRollback(RevertableObject):
1230    """
1231    This is a revertible object that always participates in rollback.
1232    It's used when a revertable object is created by an object that
1233    doesn't participate in the rollback system.
1234    """
1235
1236    def __new__(cls, *args, **kwargs):
1237        self = super(AlwaysRollback, cls).__new__(cls)
1238
1239        log = renpy.game.log
1240        if log is not None:
1241            del log.mutated[id(self)]
1242
1243        return self
1244
1245
1246class RollbackRandom(random.Random):
1247    """
1248    This is used for Random objects returned by renpy.random.Random.
1249    """
1250
1251    def __init__(self):
1252        log = renpy.game.log
1253
1254        if log is not None:
1255            log.mutated[id(self)] = None
1256
1257        super(RollbackRandom, self).__init__()
1258
1259    def _clean(self):
1260        return self.getstate()
1261
1262    def _compress(self, clean):
1263        return clean
1264
1265    def _rollback(self, compressed):
1266        super(RollbackRandom, self).setstate(compressed)
1267
1268    setstate = mutator(random.Random.setstate)
1269
1270    if PY2:
1271        jumpahead = mutator(random.Random.jumpahead)
1272
1273    getrandbits = mutator(random.Random.getrandbits)
1274    seed = mutator(random.Random.seed)
1275    random = mutator(random.Random.random)
1276
1277    def Random(self, seed=None):
1278        """
1279        Returns a new RNG object separate from the main one.
1280        """
1281
1282        if seed is None:
1283            seed = self.random()
1284
1285        new = RollbackRandom()
1286        new.seed(seed)
1287        return new
1288
1289
1290class DetRandom(random.Random):
1291    """
1292    This is renpy.random.
1293    """
1294
1295    def __init__(self):
1296        super(DetRandom, self).__init__()
1297        self.stack = [ ]
1298
1299    def random(self):
1300
1301        if self.stack:
1302            rv = self.stack.pop()
1303        else:
1304            rv = super(DetRandom, self).random()
1305
1306        log = renpy.game.log
1307
1308        if log.current is not None:
1309            log.current.random.append(rv)
1310
1311        return rv
1312
1313    def pushback(self, l):
1314        """
1315        Pushes the random numbers in l onto the stack so they will be generated
1316        in the order given.
1317        """
1318
1319        ll = l[:]
1320        ll.reverse()
1321
1322        self.stack.extend(ll)
1323
1324    def reset(self):
1325        """
1326        Resets the RNG, removing all of the pushbacked numbers.
1327        """
1328
1329        del self.stack[:]
1330
1331    def Random(self, seed=None):
1332        """
1333        Returns a new RNG object separate from the main one.
1334        """
1335
1336        if seed is None:
1337            seed = self.random()
1338
1339        new = RollbackRandom()
1340        new.seed(seed)
1341        return new
1342
1343
1344rng = DetRandom()
1345
1346# This is the code that actually handles the logging and managing
1347# of the rollbacks.
1348
1349generation = time.time()
1350serial = 0
1351
1352
1353class Rollback(renpy.object.Object):
1354    """
1355    Allows the state of the game to be rolled back to the point just
1356    before a node began executing.
1357
1358    @ivar context: A shallow copy of the context we were in before
1359    we started executing the node. (Shallow copy also includes
1360    a copy of the associated SceneList.)
1361
1362    @ivar objects: A list of tuples, each containing an object and a
1363    token of information that, when passed to the rollback method on
1364    that object, causes that object to rollback.
1365
1366    @ivar store: A list of updates to store that will cause the state
1367    of the store to be rolled back to the start of node
1368    execution. This is a list of tuples, either (key, value) tuples
1369    representing a value that needs to be assigned to a key, or (key,)
1370    tuples that mean the key should be deleted.
1371
1372    @ivar checkpoint: True if this is a user-visible checkpoint,
1373    false otherwise.
1374
1375    @ivar purged: True if purge_unreachable has already been called on
1376    this Rollback, False otherwise.
1377
1378    @ivar random: A list of random numbers that were generated during the
1379    execution of this element.
1380    """
1381
1382    __version__ = 5
1383
1384    identifier = None
1385    not_greedy = False
1386
1387    def __init__(self):
1388
1389        super(Rollback, self).__init__()
1390
1391        self.context = renpy.game.context().rollback_copy()
1392
1393        self.objects = [ ]
1394        self.purged = False
1395        self.random = [ ]
1396        self.forward = None
1397
1398        # A map of maps name -> (variable -> value)
1399        self.stores = { }
1400
1401        # A map from store name to the changes to ever_been_changed that
1402        # need to be reverted.
1403        self.delta_ebc = { }
1404
1405        # If true, we retain the data in this rollback when a load occurs.
1406        self.retain_after_load = False
1407
1408        # True if this is a checkpoint we can roll back to.
1409        self.checkpoint = False
1410
1411        # True if this is a hard checkpoint, where the rollback counter
1412        # decreases.
1413        self.hard_checkpoint = False
1414
1415        # True if this is a not-greedy checkpoint, which should end
1416        # rollbacks that occur in greedy mode.
1417        self.not_greedy = False
1418
1419        # A unique identifier for this rollback object.
1420
1421        global serial
1422        self.identifier = (generation, serial)
1423        serial += 1
1424
1425    def after_upgrade(self, version):
1426
1427        if version < 2:
1428            self.stores = { "store" : { } }
1429
1430            for i in self.store:
1431                if len(i) == 2:
1432                    k, v = i
1433                    self.stores["store"][k] = v
1434                else:
1435                    k, = i
1436                    self.stores["store"][k] = deleted
1437
1438        if version < 3:
1439            self.retain_after_load = False
1440
1441        if version < 4:
1442            self.hard_checkpoint = self.checkpoint
1443
1444        if version < 5:
1445            self.delta_ebc = { }
1446
1447    def purge_unreachable(self, reachable, wait):
1448        """
1449        Adds objects that are reachable from the store of this
1450        rollback to the set of reachable objects, and purges
1451        information that is stored about totally unreachable objects.
1452
1453        Returns True if this is the first time this method has been
1454        called, or False if it has already been called once before.
1455        """
1456
1457        if self.purged:
1458            return False
1459
1460        self.purged = True
1461
1462        # Add objects reachable from the stores. (Objects that might be
1463        # unreachable at the moment.)
1464        for changes in self.stores.values():
1465            for _k, v in changes.items():
1466                if v is not deleted:
1467                    reached(v, reachable, wait)
1468
1469        # Add in objects reachable through the context.
1470        reached(self.context.info, reachable, wait)
1471        for d in self.context.dynamic_stack:
1472            for v in d.values():
1473                reached(v, reachable, wait)
1474
1475        # Add in objects reachable through displayables.
1476        reached(self.context.scene_lists.get_all_displayables(), reachable, wait)
1477
1478        # Purge object update information for unreachable objects.
1479        new_objects = [ ]
1480
1481        objects_changed = True
1482        seen = set()
1483
1484        while objects_changed:
1485
1486            objects_changed = False
1487
1488            for o, rb in self.objects:
1489
1490                id_o = id(o)
1491
1492                if (id_o not in seen) and reachable.get(id_o, 0):
1493                    seen.add(id_o)
1494                    objects_changed = True
1495
1496                    new_objects.append((o, rb))
1497                    reached(rb, reachable, wait)
1498
1499        del self.objects[:]
1500        self.objects.extend(new_objects)
1501
1502        return True
1503
1504    def rollback(self):
1505        """
1506        Reverts the state of the game to what it was at the start of the
1507        previous checkpoint.
1508        """
1509
1510        for obj, roll in reversed(self.objects):
1511
1512            if roll is not None:
1513                obj._rollback(roll)
1514
1515        for name, changes in self.stores.items():
1516            store = store_dicts.get(name, None)
1517            if store is None:
1518                continue
1519
1520            for name, value in changes.items():
1521                if value is deleted:
1522                    if name in store:
1523                        del store[name]
1524                else:
1525                    store[name] = value
1526
1527        for name, changes in self.delta_ebc.items():
1528
1529            store = store_dicts.get(name, None)
1530            if store is None:
1531                continue
1532
1533            store.ever_been_changed -= changes
1534
1535        rng.pushback(self.random)
1536
1537        renpy.game.contexts.pop()
1538        renpy.game.contexts.append(self.context)
1539
1540    def rollback_control(self):
1541        """
1542        This rolls back only the control information, while leaving
1543        the data information intact.
1544        """
1545
1546        renpy.game.contexts.pop()
1547        renpy.game.contexts.append(self.context)
1548
1549
1550class RollbackLog(renpy.object.Object):
1551    """
1552    This class manages the list of Rollback objects.
1553
1554    @ivar log: The log of rollback objects.
1555
1556    @ivar current: The current rollback object. (Equivalent to
1557    log[-1])
1558
1559    @ivar rollback_limit: The number of steps left that we can
1560    interactively rollback.
1561
1562    Not serialized:
1563
1564    @ivar mutated: A dictionary that maps object ids to a tuple of
1565    (weakref to object, information needed to rollback that object)
1566    """
1567
1568    __version__ = 5
1569
1570    nosave = [ 'old_store', 'mutated', 'identifier_cache' ]
1571    identifier_cache = None
1572    force_checkpoint = False
1573
1574    def __init__(self):
1575
1576        super(RollbackLog, self).__init__()
1577
1578        self.log = [ ]
1579        self.current = None
1580        self.mutated = { }
1581        self.rollback_limit = 0
1582        self.rollback_is_fixed = False
1583        self.checkpointing_suspended = False
1584        self.fixed_rollback_boundary = None
1585        self.forward = [ ]
1586        self.old_store = { }
1587
1588        # Did we just do a roll forward?
1589        self.rolled_forward = False
1590
1591        # Reset the RNG on the creation of a new game.
1592        rng.reset()
1593
1594        # True if we should retain data from here to the next checkpoint
1595        # on load.
1596        self.retain_after_load_flag = False
1597
1598        # Has there been an interaction since the last time this log was
1599        # reset?
1600        self.did_interaction = True
1601
1602        # Should we force a checkpoint before completing the current
1603        # statement.
1604        self.force_checkpoint = False
1605
1606    def after_setstate(self):
1607        self.mutated = { }
1608        self.rolled_forward = False
1609
1610    def after_upgrade(self, version):
1611        if version < 2:
1612            self.ever_been_changed = { "store" : set(self.ever_been_changed) }
1613        if version < 3:
1614            self.rollback_is_fixed = False
1615            self.fixed_rollback_boundary = None
1616        if version < 4:
1617            self.retain_after_load_flag = False
1618
1619        if version < 5:
1620
1621            # We changed what the rollback limit represents, so recompute it
1622            # here.
1623            if self.rollback_limit:
1624                nrbl = 0
1625
1626                for rb in self.log[-self.rollback_limit:]:
1627                    if rb.hard_checkpoint:
1628                        nrbl += 1
1629
1630                self.rollback_limit = nrbl
1631
1632    def begin(self, force=False):
1633        """
1634        Called before a node begins executing, to indicate that the
1635        state needs to be saved for rollbacking.
1636        """
1637
1638        self.identifier_cache = None
1639
1640        context = renpy.game.context()
1641
1642        if not context.rollback:
1643            return
1644
1645        # We only begin a checkpoint if the previous statement reached a checkpoint,
1646        # or an interaction took place. (Or we're forced.)
1647        ignore = True
1648
1649        if force:
1650            ignore = False
1651        elif self.did_interaction:
1652            ignore = False
1653        elif self.current is not None:
1654            if self.current.checkpoint:
1655                ignore = False
1656            elif self.current.retain_after_load:
1657                ignore = False
1658
1659        if ignore:
1660            return
1661
1662        self.did_interaction = False
1663
1664        if self.current is not None:
1665            self.complete(True)
1666        else:
1667            begin_stores()
1668
1669        # If the log is too long, prune it.
1670        while len(self.log) > renpy.config.rollback_length:
1671            self.log.pop(0)
1672
1673        # check for the end of fixed rollback
1674        if self.log and self.log[-1] == self.current:
1675
1676            if self.current.context.current == self.fixed_rollback_boundary:
1677                self.rollback_is_fixed = False
1678
1679            elif self.rollback_is_fixed and not self.forward:
1680                # A lack of rollback data in fixed rollback mode ends rollback.
1681                self.fixed_rollback_boundary = self.current.context.current
1682                self.rollback_is_fixed = False
1683
1684        self.current = Rollback()
1685        self.current.retain_after_load = self.retain_after_load_flag
1686
1687        self.log.append(self.current)
1688
1689        self.mutated.clear()
1690
1691        # Flag a mutation as having happened. This is used by the
1692        # save code.
1693        global mutate_flag
1694        mutate_flag = True
1695
1696        self.rolled_forward = False
1697
1698    def replace_node(self, old, new):
1699        """
1700        Replaces references to the `old` ast node with a reference to the
1701        `new` ast node.
1702        """
1703
1704        for i in self.log:
1705            i.context.replace_node(old, new)
1706
1707    def complete(self, begin=False):
1708        """
1709        Called after a node is finished executing, before a save
1710        begins, or right before a rollback is attempted. This may be
1711        called more than once between calls to begin, and should always
1712        be called after an update to the store but before a rollback
1713        occurs.
1714
1715        `begin`
1716            Should be true if called from begin().
1717        """
1718
1719        if self.force_checkpoint:
1720            self.checkpoint(hard=False)
1721            self.force_checkpoint = False
1722
1723        # Update self.current.stores with the changes from each store.
1724        # Also updates .ever_been_changed.
1725        for name, sd in store_dicts.items():
1726            self.current.stores[name], self.current.delta_ebc[name] = sd.get_changes(begin)
1727
1728        # Update the list of mutated objects and what we need to do to
1729        # restore them.
1730
1731        for _i in range(4):
1732
1733            del self.current.objects[:]
1734
1735            try:
1736                for _k, v in self.mutated.items():
1737
1738                    if v is None:
1739                        continue
1740
1741                    (ref, clean) = v
1742
1743                    obj = ref()
1744                    if obj is None:
1745                        continue
1746
1747                    compressed = obj._compress(clean)
1748                    self.current.objects.append((obj, compressed))
1749
1750                break
1751
1752            except RuntimeError:
1753                # This can occur when self.mutated is changed as we're
1754                # iterating over it.
1755                pass
1756
1757    def get_roots(self):
1758        """
1759        Return a map giving the current roots of the store. This is a
1760        map from a variable name in the store to the value of that
1761        variable. A variable is only in this map if it has ever been
1762        changed since the init phase finished.
1763        """
1764
1765        rv = { }
1766
1767        for store_name, sd in store_dicts.items():
1768            for name in sd.ever_been_changed:
1769                if name in sd:
1770                    rv[store_name + "." + name] = sd[name]
1771                else:
1772                    rv[store_name + "." + name] = deleted
1773
1774        for i in reversed(renpy.game.contexts[1:]):
1775            i.pop_dynamic_roots(rv)
1776
1777        return rv
1778
1779    def purge_unreachable(self, roots, wait=None):
1780        """
1781        This is called to purge objects that are unreachable from the
1782        roots from the object rollback lists inside the Rollback entries.
1783
1784        This should be called immediately after complete(), so that there
1785        are no changes queued up.
1786        """
1787
1788        reachable = { }
1789
1790        reached_vars(roots, reachable, wait)
1791
1792        revlog = self.log[:]
1793        revlog.reverse()
1794
1795        for i in revlog:
1796            if not i.purge_unreachable(reachable, wait):
1797                break
1798
1799    def in_rollback(self):
1800        if self.forward:
1801            return True
1802        else:
1803            return False
1804
1805    def in_fixed_rollback(self):
1806        return self.rollback_is_fixed
1807
1808    def forward_info(self):
1809        """
1810        Returns the current forward info, if any.
1811        """
1812
1813        if self.forward:
1814
1815            name, data = self.forward[0]
1816
1817            if self.current.context.current == name:
1818                return data
1819
1820        return None
1821
1822    def checkpoint(self, data=None, keep_rollback=False, hard=True):
1823        """
1824        Called to indicate that this is a checkpoint, which means
1825        that the user may want to rollback to just before this
1826        node.
1827        """
1828
1829        if self.checkpointing_suspended:
1830            hard = False
1831
1832        self.retain_after_load_flag = False
1833
1834        if self.current.checkpoint:
1835            return
1836
1837        if not renpy.game.context().rollback:
1838            return
1839
1840        self.current.checkpoint = True
1841
1842        if hard and (not self.current.hard_checkpoint):
1843            if self.rollback_limit < renpy.config.hard_rollback_limit:
1844                self.rollback_limit += 1
1845
1846            if hard == "not_greedy":
1847                self.current.not_greedy = True
1848            else:
1849                self.current.hard_checkpoint = hard
1850
1851        if self.in_fixed_rollback() and self.forward:
1852            # use data from the forward stack
1853            fwd_name, fwd_data = self.forward[0]
1854            if self.current.context.current == fwd_name:
1855                self.current.forward = fwd_data
1856                self.forward.pop(0)
1857            else:
1858                self.current.forward = data
1859                del self.forward[:]
1860
1861        elif data is not None:
1862            if self.forward:
1863                # If the data is the same, pop it from the forward stack.
1864                # Otherwise, clear the forward stack.
1865                fwd_name, fwd_data = self.forward[0]
1866
1867                if (self.current.context.current == fwd_name
1868                    and data == fwd_data
1869                    and (keep_rollback or self.rolled_forward)
1870                    ):
1871                    self.forward.pop(0)
1872                else:
1873                    del self.forward[:]
1874
1875            # Log the data in case we roll back again.
1876            self.current.forward = data
1877
1878    def suspend_checkpointing(self, flag):
1879        """
1880        Called to temporarily suspend checkpointing, so any rollback
1881        will jump to prior to this statement
1882        """
1883
1884        self.checkpointing_suspended = flag
1885
1886    def block(self, purge=False):
1887        """
1888        Called to indicate that the user should not be able to rollback
1889        through this checkpoint.
1890        """
1891
1892        self.rollback_limit = 0
1893        renpy.game.context().force_checkpoint = True
1894
1895        if purge:
1896            del self.log[:]
1897
1898    def retain_after_load(self):
1899        """
1900        Called to return data from this statement until the next checkpoint
1901        when the game is loaded.
1902        """
1903
1904        if renpy.display.predict.predicting:
1905            return
1906
1907        self.retain_after_load_flag = True
1908        self.current.retain_after_load = True
1909        renpy.game.context().force_checkpoint = True
1910
1911    def fix_rollback(self):
1912        if not self.rollback_is_fixed and len(self.log) > 1:
1913            self.fixed_rollback_boundary = self.log[-2].context.current
1914
1915    def can_rollback(self):
1916        """
1917        Returns True if we can rollback.
1918        """
1919
1920        return self.rollback_limit > 0
1921
1922    def load_failed(self):
1923        """
1924        This is called to try to recover when rollback fails.
1925        """
1926
1927        lfl = renpy.config.load_failed_label
1928        if callable(lfl):
1929            lfl = lfl()
1930
1931        if not lfl:
1932            raise Exception("Couldn't find a place to stop rolling back. Perhaps the script changed in an incompatible way?")
1933
1934        rb = self.log.pop()
1935        rb.rollback()
1936
1937        while renpy.exports.call_stack_depth():
1938            renpy.exports.pop_call()
1939
1940        renpy.game.contexts[0].force_checkpoint = True
1941        renpy.game.contexts[0].goto_label(lfl)
1942
1943        raise renpy.game.RestartTopContext()
1944
1945    def rollback(self, checkpoints, force=False, label=None, greedy=True, on_load=False, abnormal=True, current_label=None):
1946        """
1947        This rolls the system back to the first valid rollback point
1948        after having rolled back past the specified number of checkpoints.
1949
1950        If we're currently executing code, it's expected that complete()
1951        will be called before a rollback is attempted.
1952
1953        force makes us throw an exception if we can't find a place to stop
1954        rolling back, otherwise if we run out of log this call has no
1955        effect.
1956
1957        `label`
1958            A label that is called after rollback has finished, if the
1959            label exists.
1960
1961        `greedy`
1962            If true, rollback will keep going until just after the last
1963            checkpoint. If False, it will stop immediately before the
1964            current statement.
1965
1966        `on_load`
1967            Should be true if this rollback is being called in response to a
1968            load. Used to implement .retain_after_load()
1969
1970        `abnormal`
1971            If true, treats this as an abnormal event, suppressing transitions
1972            and so on.
1973
1974        `current_label`
1975            A lable that is called when control returns to the current statement,
1976            after rollback. (At most one of `current_label` and `label` can be
1977            provided.)
1978        """
1979
1980        # If we have exceeded the rollback limit, and don't have force,
1981        # give up.
1982        if checkpoints and (self.rollback_limit <= 0) and (not force):
1983            return
1984
1985        self.suspend_checkpointing(False)
1986        # will always rollback to before suspension
1987
1988        self.purge_unreachable(self.get_roots())
1989
1990        revlog = [ ]
1991
1992        # Find the place to roll back to.
1993        while self.log:
1994            rb = self.log.pop()
1995            revlog.append(rb)
1996
1997            if rb.hard_checkpoint:
1998                self.rollback_limit -= 1
1999
2000            if rb.hard_checkpoint or (on_load and rb.checkpoint):
2001                checkpoints -= 1
2002
2003            if checkpoints <= 0:
2004                if renpy.game.script.has_label(rb.context.current):
2005                    break
2006
2007        else:
2008            # Otherwise, just give up.
2009
2010            revlog.reverse()
2011            self.log.extend(revlog)
2012
2013            if force:
2014                self.load_failed()
2015            else:
2016                print("Can't find a place to rollback to. Not rolling back.")
2017
2018            return
2019
2020        force_checkpoint = False
2021
2022        # Try to rollback to just after the previous checkpoint.
2023        while greedy and self.log and (self.rollback_limit > 0):
2024
2025            rb = self.log[-1]
2026
2027            if not renpy.game.script.has_label(rb.context.current):
2028                break
2029
2030            if rb.hard_checkpoint:
2031                break
2032
2033            if rb.not_greedy:
2034                break
2035
2036            revlog.append(self.log.pop())
2037
2038        # Decide if we're replacing the current context (rollback command),
2039        # or creating a new set of contexts (loading).
2040        if renpy.game.context().rollback:
2041            replace_context = False
2042            other_contexts = [ ]
2043
2044        else:
2045            replace_context = True
2046            other_contexts = renpy.game.contexts[1:]
2047            renpy.game.contexts = renpy.game.contexts[0:1]
2048
2049        if on_load and revlog[-1].retain_after_load:
2050            retained = revlog.pop()
2051            self.retain_after_load_flag = True
2052        else:
2053            retained = None
2054
2055        come_from = None
2056
2057        if current_label is not None:
2058            come_from = renpy.game.context().current
2059            label = current_label
2060
2061        # Actually roll things back.
2062        for rb in revlog:
2063            rb.rollback()
2064
2065            if (rb.context.current == self.fixed_rollback_boundary) and (rb.context.current):
2066                self.rollback_is_fixed = True
2067
2068            if rb.forward is not None:
2069                self.forward.insert(0, (rb.context.current, rb.forward))
2070
2071        if retained is not None:
2072            retained.rollback_control()
2073            self.log.append(retained)
2074
2075        if (label is not None) and (come_from is None):
2076            come_from = renpy.game.context().current
2077
2078        if come_from is not None:
2079            renpy.game.context().come_from(come_from, label)
2080
2081        # Disable the next transition, as it's pointless. (Only when not used with a label.)
2082        renpy.game.interface.suppress_transition = abnormal
2083
2084        # If necessary, reset the RNG.
2085        if force:
2086            rng.reset()
2087            del self.forward[:]
2088
2089        # Flag that we're in the transition immediately after a rollback.
2090        renpy.game.after_rollback = abnormal
2091
2092        # Stop the sounds.
2093        renpy.audio.audio.rollback()
2094
2095        # Stop any hiding in progress.
2096        for i in renpy.game.contexts:
2097            i.scene_lists.remove_all_hidden()
2098
2099        renpy.game.contexts.extend(other_contexts)
2100
2101        renpy.exports.execute_default_statement(False)
2102
2103        self.mutated.clear()
2104        begin_stores()
2105
2106        # Restart the context or the top context.
2107        if replace_context:
2108
2109            if force_checkpoint:
2110                renpy.game.contexts[0].force_checkpoint = True
2111
2112            self.current = Rollback()
2113            self.current.context = renpy.game.contexts[0].rollback_copy()
2114
2115            if self.log is not None:
2116                self.log.append(self.current)
2117
2118            raise renpy.game.RestartTopContext()
2119
2120        else:
2121
2122            self.current = Rollback()
2123            self.current.context = renpy.game.context().rollback_copy()
2124
2125            if self.log is not None:
2126                self.log.append(self.current)
2127
2128            if force_checkpoint:
2129                renpy.game.context().force_checkpoint = True
2130
2131            raise renpy.game.RestartContext()
2132
2133    def freeze(self, wait=None):
2134        """
2135        This is called to freeze the store and the log, in preparation
2136        for serialization. The next call on log should either be
2137        unfreeze (called after a serialization reload) or discard_freeze()
2138        (called after the save is complete).
2139        """
2140
2141        # Purge unreachable objects, so we don't save them.
2142        self.complete(False)
2143        roots = self.get_roots()
2144        self.purge_unreachable(roots, wait=wait)
2145
2146        # The current is not purged.
2147        self.current.purged = False
2148
2149        return roots
2150
2151    def discard_freeze(self):
2152        """
2153        Called to indicate that we will not be restoring from the
2154        frozen state.
2155        """
2156
2157    def unfreeze(self, roots, label=None):
2158        """
2159        Used to unfreeze the game state after a load of this log
2160        object. This call will always throw an exception. If we're
2161        lucky, it's the one that indicates load was successful.
2162
2163        @param roots: The roots returned from freeze.
2164
2165        @param label: The label that is jumped to in the game script
2166        after rollback has finished, if it exists.
2167        """
2168
2169        # Fix up old screens.
2170        renpy.display.screen.before_restart() # @UndefinedVariable
2171
2172        # Set us up as the game log.
2173        renpy.game.log = self
2174
2175        clean_stores()
2176        renpy.translation.init_translation()
2177
2178        for name, value in roots.items():
2179
2180            if "." in name:
2181                store_name, name = name.rsplit(".", 1)
2182            else:
2183                store_name = "store"
2184
2185            if store_name not in store_dicts:
2186                continue
2187
2188            store = store_dicts[store_name]
2189            store.ever_been_changed.add(name)
2190
2191            if value is deleted:
2192                if name in store:
2193                    del store[name]
2194            else:
2195                store[name] = value
2196
2197        # Now, rollback to an acceptable point.
2198
2199        greedy = renpy.session.pop("_greedy_rollback", False)
2200        self.rollback(0, force=True, label=label, greedy=greedy, on_load=True)
2201
2202        # Because of the rollback, we never make it this far.
2203
2204    def build_identifier_cache(self):
2205
2206        if self.identifier_cache is not None:
2207            return
2208
2209        rollback_limit = self.rollback_limit
2210        checkpoints = 1
2211
2212        self.identifier_cache = { }
2213
2214        for i in reversed(self.log):
2215
2216            if i.identifier is not None:
2217                if renpy.game.script.has_label(i.context.current):
2218                    self.identifier_cache[i.identifier] = checkpoints
2219
2220            if i.hard_checkpoint:
2221                checkpoints += 1
2222
2223            if i.checkpoint:
2224                rollback_limit -= 1
2225
2226            if not rollback_limit:
2227                break
2228
2229    def get_identifier_checkpoints(self, identifier):
2230        self.build_identifier_cache()
2231        return self.identifier_cache.get(identifier, None)
2232
2233
2234def py_exec_bytecode(bytecode, hide=False, globals=None, locals=None, store="store"): # @ReservedAssignment
2235
2236    if hide:
2237        locals = { } # @ReservedAssignment
2238
2239    if globals is None:
2240        globals = store_dicts[store] # @ReservedAssignment
2241
2242    if locals is None:
2243        locals = globals # @ReservedAssignment
2244
2245    exec(bytecode, globals, locals)
2246
2247
2248def py_exec(source, hide=False, store=None):
2249
2250    if store is None:
2251        store = store_dicts["store"]
2252
2253    if hide:
2254        locals = { } # @ReservedAssignment
2255    else:
2256        locals = store # @ReservedAssignment
2257
2258    exec(py_compile(source, 'exec'), store, locals)
2259
2260
2261def py_eval_bytecode(bytecode, globals=None, locals=None): # @ReservedAssignment
2262
2263    if globals is None:
2264        globals = store_dicts["store"] # @ReservedAssignment
2265
2266    if locals is None:
2267        locals = globals # @ReservedAssignment
2268
2269    return eval(bytecode, globals, locals)
2270
2271
2272def py_eval(code, globals=None, locals=None): # @ReservedAssignment
2273    if isinstance(code, basestring):
2274        code = py_compile(code, 'eval')
2275
2276    return py_eval_bytecode(code, globals, locals)
2277
2278
2279def store_eval(code, globals=None, locals=None):
2280
2281    if globals is None:
2282        globals = sys._getframe(1).f_globals
2283
2284    return py_eval(code, globals, locals)
2285
2286
2287def raise_at_location(e, loc):
2288    """
2289    Raises `e` (which must be an Exception object) at location `loc`.
2290
2291    `loc`
2292        A location, which should be a (filename, line_number) tuple.
2293    """
2294
2295    filename, line = loc
2296
2297    node = ast.parse("raise e", filename)
2298    ast.increment_lineno(node, line - 1)
2299    code = compile(node, filename, 'exec')
2300
2301    # PY3 - need to change to exec().
2302    exec(code, { "e" : e })
2303
2304
2305# This was used to proxy accesses to the store. Now it's kept around to deal
2306# with cases where it might have leaked into a pickle.
2307class StoreProxy(object):
2308
2309    def __getattr__(self, k):
2310        return getattr(renpy.store, k) # @UndefinedVariable
2311
2312    def __setattr__(self, k, v):
2313        setattr(renpy.store, k, v) # @UndefinedVariable
2314
2315    def __delattr__(self, k):
2316        delattr(renpy.store, k) # @UndefinedVariable
2317
2318
2319if PY2:
2320
2321    # Code for pickling bound methods.
2322    def method_pickle(method):
2323        name = method.im_func.__name__
2324
2325        obj = method.im_self
2326
2327        if obj is None:
2328            obj = method.im_class
2329
2330        return method_unpickle, (obj, name)
2331
2332    def method_unpickle(obj, name):
2333        return getattr(obj, name)
2334
2335    copyreg.pickle(types.MethodType, method_pickle)
2336
2337# Code for pickling modules.
2338
2339
2340def module_pickle(module):
2341    if renpy.config.developer:
2342        raise Exception("Could not pickle {!r}.".format(module))
2343
2344    return module_unpickle, (module.__name__,)
2345
2346
2347def module_unpickle(name):
2348    return __import__(name)
2349
2350
2351copyreg.pickle(types.ModuleType, module_pickle)
2352