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######################################################################### 23# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 24# 25# When adding fields to a class in an __init__ method, we need to ensure that 26# field is copied in the copy() method. 27 28from __future__ import division, absolute_import, with_statement, print_function, unicode_literals 29from renpy.compat import * 30from renpy.compat.pickle import loads, dumps 31 32import ast 33import collections 34import linecache 35import zlib 36import weakref 37 38import renpy.display 39import renpy.pyanalysis 40import renpy.sl2 41 42from renpy.display.transform import Transform, ATLTransform 43from renpy.display.layout import Fixed 44from renpy.display.predict import displayable as predict_displayable 45 46from renpy.python import py_eval_bytecode 47from renpy.pyanalysis import Analysis, NOT_CONST, LOCAL_CONST, GLOBAL_CONST, ccache 48 49import hashlib 50import time 51 52# This file contains the abstract syntax tree for a screen language 53# screen. 54 55# A serial number that makes each SLNode unique. 56serial = int(time.time() * 1000000) 57 58# A sentinel used to indicate we should use the value found in the 59# expression. 60use_expression = renpy.object.Sentinel("use_expression") 61 62# The filename that's currently being compiled. 63filename = '<screen language>' 64 65# A log that's used for profiling information. 66profile_log = renpy.log.open("profile_screen", developer=True, append=False, flush=False) 67 68 69def compile_expr(loc, node): 70 """ 71 Wraps the node in a python AST, and compiles it. 72 """ 73 74 filename = loc[0] 75 if filename in renpy.python.py3_files: 76 flags = renpy.python.py3_compile_flags 77 else: 78 flags = renpy.python.new_compile_flags 79 80 expr = ast.Expression(body=node) 81 ast.fix_missing_locations(expr) 82 return compile(expr, filename, "eval", flags, 1) 83 84 85class SLContext(renpy.ui.Addable): 86 """ 87 A context object that can be passed to the execute methods, and can also 88 be placed in renpy.ui.stack. 89 """ 90 91 def __init__(self, parent=None): 92 if parent is not None: 93 self.__dict__.update(parent.__dict__) 94 return 95 96 # The local scope that python code is evaluated in. 97 self.scope = { } 98 99 # The scope of the top-level screen. 100 self.root_scope = self.scope 101 102 # The global scope that python code is evaluated in. 103 self.globals = { } 104 105 # A list of child displayables that will be added to an outer 106 # displayable. 107 self.children = [ ] 108 109 # A map from keyword arguments to their values. 110 self.keywords = { } 111 112 # The style prefix that is given to children of this displayable. 113 self.style_prefix = None 114 115 # A cache associated with this context. The cache maps from 116 # statement serial to information associated with the statement. 117 self.new_cache = { } 118 119 # The old cache, used to take information from the old version of 120 # this displayable. 121 self.old_cache = { } 122 123 # The miss cache, used to take information that isn't present in 124 # old_cache. 125 self.miss_cache = { } 126 127 # The number of times a particular use statement has been called 128 # in the current screen. We use this to generate a unique name for 129 # each call site. 130 self.use_index = collections.defaultdict(int) 131 132 # When a constant node uses the scope, we add it to this list, so 133 # it may be reused. (If None, no list is used.) 134 self.uses_scope = None 135 136 # When a constant node has an id, we added it to this dict, so it 137 # may be reused. (If None, no dict is used.) 138 self.widgets = None 139 140 # True if we should dump debug information to the profile log. 141 self.debug = False 142 143 # True if we're predicting the screen. 144 self.predicting = False 145 146 # True if we're updating the screen. 147 self.updating = False 148 149 # A list of nodes we've predicted, for cases where predicting more than 150 # once could be a performance problem. 151 self.predicted = set() 152 153 # True if we're in a true showif block, False if we're in a false showif 154 # block, or None if we're not in a showif block. 155 self.showif = None 156 157 # True if there was a failure in this statement or any of its children. 158 # Fails can only occur when predicting, as otherwise an exception 159 # would be thrown. 160 self.fail = False 161 162 # The parent context of a use statement with a block. 163 self.parent = None 164 165 # The use statement containing the transcluded block. 166 self.transclude = None 167 168 # True if it's unlikely this node will run. This is used in prediction 169 # to speed things up. 170 self.unlikely = False 171 172 # The old and new generations of the use_cache. 173 self.new_use_cache = { } 174 self.old_use_cache = { } 175 176 def add(self, d, key): 177 self.children.append(d) 178 179 def close(self, d): 180 raise Exception("Spurious ui.close().") 181 182 183class SLNode(object): 184 """ 185 The base class for screen language nodes. 186 """ 187 188 # The type of constant this node is. 189 constant = GLOBAL_CONST 190 191 # True if this node has at least one keyword that applies to its 192 # parent. False otherwise. 193 has_keyword = False 194 195 # True if this node should be the last keyword parsed. 196 last_keyword = False 197 198 def __init__(self, loc): 199 global serial 200 serial += 1 201 202 # A unique serial number assigned to this node. 203 self.serial = serial 204 205 # The location of this node, a (file, line) tuple. 206 self.location = loc 207 208 def instantiate(self, transclude): 209 """ 210 Instantiates a new instance of this class, copying the global 211 attributes of this class onto the new instance. 212 """ 213 214 cls = type(self) 215 216 rv = cls.__new__(cls) 217 rv.serial = self.serial 218 rv.location = self.location 219 220 return rv 221 222 def copy(self, transclude): 223 """ 224 Makes a copy of this node. 225 226 `transclude` 227 The constness of transclude statements. 228 """ 229 230 raise Exception("copy not implemented by " + type(self).__name__) 231 232 def report_traceback(self, name, last): 233 if last: 234 return None 235 236 filename, line = self.location 237 238 return [ (filename, line, name, None) ] 239 240 def analyze(self, analysis): 241 """ 242 Performs static analysis on Python code used in this statement. 243 """ 244 245 # By default, does nothing. 246 247 def prepare(self, analysis): 248 """ 249 This should be called before the execute code is called, and again 250 after init-level code (like the code in a .rpym module or an init 251 python block) is called. 252 253 `analysis` 254 A pyanalysis.Analysis object containing the analysis of this screen. 255 """ 256 257 # By default, does nothing. 258 259 def execute(self, context): 260 """ 261 Execute this node, updating context as appropriate. 262 """ 263 264 raise Exception("execute not implemented by " + type(self).__name__) 265 266 def keywords(self, context): 267 """ 268 Execute this node, updating context.keywords as appropriate. 269 """ 270 271 # By default, does nothing. 272 return 273 274 def copy_on_change(self, cache): 275 """ 276 Flags the displayables that are created by this node and its children 277 as copy-on-change. 278 """ 279 280 return 281 282 def debug_line(self): 283 """ 284 Writes information about the line we're on to the debug log. 285 """ 286 287 filename, lineno = self.location 288 full_filename = renpy.exports.unelide_filename(filename) 289 290 line = linecache.getline(full_filename, lineno) or "" 291 line = line.decode("utf-8") 292 293 profile_log.write(" %s:%d %s", filename, lineno, line.rstrip()) 294 295 if self.constant: 296 profile_log.write(" potentially constant") 297 298 def used_screens(self, callback): 299 """ 300 Calls callback with the name of each screen this node and its 301 children use. 302 """ 303 304 return 305 306 def has_transclude(self): 307 """ 308 Returns true if this node is a transclude or has a transclude as a child. 309 """ 310 311 return False 312 313 def has_python(self): 314 """ 315 Returns true if this node is Python or has a python node as a child. 316 """ 317 318 return False 319 320 321# A sentinel used to indicate a keyword argument was not given. 322NotGiven = renpy.object.Sentinel("NotGiven") 323 324 325class SLBlock(SLNode): 326 """ 327 Represents a screen language block that can contain keyword arguments 328 and child displayables. 329 """ 330 331 # RawBlock from parse or None if not present. 332 atl_transform = None 333 334 def __init__(self, loc): 335 SLNode.__init__(self, loc) 336 337 # A list of keyword argument, expr tuples. 338 self.keyword = [ ] 339 340 # A list of child SLNodes. 341 self.children = [ ] 342 343 def instantiate(self, transclude): 344 rv = SLNode.instantiate(self, transclude) 345 rv.keyword = self.keyword 346 rv.children = [ i.copy(transclude) for i in self.children ] 347 rv.atl_transform = self.atl_transform 348 349 return rv 350 351 def copy(self, transclude): 352 return self.instantiate(transclude) 353 354 def analyze(self, analysis): 355 356 for i in self.children: 357 i.analyze(analysis) 358 359 def prepare(self, analysis): 360 361 for i in self.children: 362 i.prepare(analysis) 363 self.constant = min(self.constant, i.constant) 364 365 # Compile the keywords. 366 367 keyword_values = { } 368 keyword_keys = [ ] 369 keyword_exprs = [ ] 370 371 for k, expr in self.keyword: 372 373 node = ccache.ast_eval(expr) 374 375 const = analysis.is_constant(node) 376 377 if const == GLOBAL_CONST: 378 keyword_values[k] = py_eval_bytecode(compile_expr(self.location, node)) 379 else: 380 keyword_keys.append(ast.Str(s=k)) 381 keyword_exprs.append(node) # Will be compiled as part of ast.Dict below. 382 383 self.constant = min(self.constant, const) 384 385 if keyword_values: 386 self.keyword_values = keyword_values 387 else: 388 self.keyword_values = None 389 390 if keyword_keys: 391 node = ast.Dict(keys=keyword_keys, values=keyword_exprs) 392 ast.copy_location(node, keyword_exprs[0]) 393 self.keyword_exprs = compile_expr(self.location, node) 394 else: 395 self.keyword_exprs = None 396 397 self.has_keyword = bool(self.keyword) 398 self.keyword_children = [ ] 399 400 if self.atl_transform is not None: 401 self.has_keyword = True 402 403 self.atl_transform.mark_constant() 404 const = self.atl_transform.constant 405 self.constant = min(self.constant, const) 406 407 was_last_keyword = False 408 for i in self.children: 409 if i.has_keyword: 410 411 if was_last_keyword: 412 raise Exception("Properties are not allowed here.") 413 414 self.keyword_children.append(i) 415 self.has_keyword = True 416 417 if i.last_keyword: 418 self.last_keyword = True 419 was_last_keyword = True 420 if not renpy.config.developer: 421 break 422 423 def execute(self, context): 424 425 # Note: SLBlock.execute() is inlined in various locations for performance 426 # reasons. 427 428 for i in self.children: 429 430 try: 431 i.execute(context) 432 except: 433 if not context.predicting: 434 raise 435 436 def keywords(self, context): 437 438 keyword_values = self.keyword_values 439 440 if keyword_values is not None: 441 context.keywords.update(keyword_values) 442 443 keyword_exprs = self.keyword_exprs 444 445 if keyword_exprs is not None: 446 context.keywords.update(eval(keyword_exprs, context.globals, context.scope)) 447 448 for i in self.keyword_children: 449 i.keywords(context) 450 451 if self.atl_transform is not None: 452 transform = ATLTransform(self.atl_transform, context=context.scope) 453 context.keywords["at"] = transform 454 455 style_prefix = context.keywords.pop("style_prefix", NotGiven) 456 457 if style_prefix is NotGiven: 458 style_prefix = context.keywords.pop("style_group", NotGiven) 459 460 if style_prefix is not NotGiven: 461 context.style_prefix = style_prefix 462 463 def copy_on_change(self, cache): 464 for i in self.children: 465 i.copy_on_change(cache) 466 467 def used_screens(self, callback): 468 for i in self.children: 469 i.used_screens(callback) 470 471 def has_transclude(self): 472 for i in self.children: 473 if i.has_transclude(): 474 return True 475 476 return False 477 478 def has_python(self): 479 return any(i.has_python() for i in self.children) 480 481 def has_noncondition_child(self): 482 """ 483 Returns true if this block has a child that is not an SLIf statement, 484 or false otherwise. 485 """ 486 487 worklist = list(self.children) 488 489 while worklist: 490 491 n = worklist.pop(0) 492 493 if type(n) is SLBlock: 494 worklist.extend(n.children) 495 elif isinstance(n, SLIf): 496 for _, block in n.entries: 497 worklist.append(block) 498 else: 499 return True 500 501 return False 502 503 def keyword_exist(self, name): 504 """ 505 Returns true if this block or it's SLIf children have parsed `name` keyword, 506 or false otherwise. 507 """ 508 509 if name in dict(self.keyword): 510 return True 511 512 for n in self.children: 513 514 if isinstance(n, SLIf): 515 if n.keyword_exist(name): 516 return True 517 518 return False 519 520 521list_or_tuple = (list, tuple) 522 523 524class SLCache(object): 525 """ 526 The type of cache associated with an SLDisplayable. 527 """ 528 529 def __init__(self): 530 531 # The displayable object created. 532 self.displayable = None 533 534 # The positional arguments that were used to create the displayable. 535 self.positional = None 536 537 # The keyword arguments that were used to created the displayable. 538 self.keywords = None 539 540 # A list of the children that were added to self.displayable. 541 self.children = None 542 543 # The outermost old transform. 544 self.outer_transform = None 545 546 # The innermost old transform. 547 self.inner_transform = None 548 549 # The transform (or list of transforms) that was used to create self.transform. 550 self.raw_transform = None 551 552 # The imagemap stack entry we reuse. 553 self.imagemap = None 554 555 # If this can be represented as a single constant displayable, 556 # do so. 557 self.constant = None 558 559 # For a constant statement, a list of our children that use 560 # the scope. 561 self.constant_uses_scope = [ ] 562 563 # For a constant statement, a map from children to widgets. 564 self.constant_widgets = { } 565 566 # True if the displayable should be re-created if its arguments 567 # or children are changed. 568 self.copy_on_change = False 569 570 # The ShowIf this statement was wrapped in the last time it was wrapped. 571 self.old_showif = None 572 573 # The SLUse that was transcluded by this SLCache statement. 574 self.transclude = None 575 576 # The style prefix used when this statement was first created. 577 self.style_prefix = None 578 579 580# A magic value that, if returned by a displayable function, is not added to 581# the parent. 582NO_DISPLAYABLE = renpy.display.layout.Null() 583 584 585class SLDisplayable(SLBlock): 586 """ 587 A screen language AST node that corresponds to a displayable being 588 added to the tree. 589 """ 590 591 hotspot = False 592 variable = None 593 594 # A list of variables that are locally constant. 595 local_constant = [ ] 596 597 def __init__(self, loc, displayable, scope=False, child_or_fixed=False, style=None, text_style=None, pass_context=False, imagemap=False, replaces=False, default_keywords={}, hotspot=False, variable=None): 598 """ 599 `displayable` 600 A function that, when called with the positional and keyword 601 arguments, causes the displayable to be displayed. 602 603 `scope` 604 If true, the scope is supplied as an argument to the displayable. 605 606 `child_or_fixed` 607 If true and the number of children of this displayable is not one, 608 the children are added to a Fixed, and the Fixed is added to the 609 displayable. 610 611 `style` 612 The base name of the main style. 613 614 `pass_context` 615 If given, the context is passed in as the first positional argument 616 of the displayable. 617 618 `imagemap` 619 True if this is an imagemap, and should be handled as one. 620 621 `hotspot` 622 True if this is a hotspot that depends on the imagemap it was 623 first displayed with. 624 625 `replaces` 626 True if the object this displayable replaces should be 627 passed to it. 628 629 `default_keywords` 630 The default keyword arguments to supply to the displayable. 631 632 `variable` 633 A variable that the main displayable is assigned to. 634 """ 635 636 SLBlock.__init__(self, loc) 637 638 self.displayable = displayable 639 640 self.scope = scope 641 self.child_or_fixed = child_or_fixed 642 self.style = style 643 self.pass_context = pass_context 644 self.imagemap = imagemap 645 self.hotspot = hotspot 646 self.replaces = replaces 647 self.default_keywords = default_keywords 648 self.variable = variable 649 650 # Positional argument expressions. 651 self.positional = [ ] 652 653 def copy(self, transclude): 654 rv = self.instantiate(transclude) 655 656 rv.displayable = self.displayable 657 rv.scope = self.scope 658 rv.child_or_fixed = self.child_or_fixed 659 rv.style = self.style 660 rv.pass_context = self.pass_context 661 rv.imagemap = self.imagemap 662 rv.hotspot = self.hotspot 663 rv.replaces = self.replaces 664 rv.default_keywords = self.default_keywords 665 rv.variable = self.variable 666 rv.positional = self.positional 667 668 return rv 669 670 def analyze(self, analysis): 671 672 if self.imagemap: 673 674 const = GLOBAL_CONST 675 676 for _k, expr in self.keyword: 677 const = min(const, analysis.is_constant_expr(expr)) 678 679 analysis.push_control(imagemap=(const != GLOBAL_CONST)) 680 681 if self.hotspot: 682 self.constant = min(analysis.imagemap(), self.constant) 683 684 SLBlock.analyze(self, analysis) 685 686 if self.imagemap: 687 analysis.pop_control() 688 689 # If we use a scope, store the local constants that need to be 690 # kept and placed into the scope. 691 if self.scope: 692 self.local_constant = list(analysis.local_constant) 693 694 if self.variable is not None: 695 const = self.constant 696 697 for i in self.positional: 698 const = min(self.constant, analysis.is_constant_expr(i)) 699 700 for k, v in self.keyword: 701 const = min(self.constant, analysis.is_constant_expr(v)) 702 703 if self.keyword_exist("id"): 704 const = NOT_CONST 705 706 if const == LOCAL_CONST: 707 analysis.mark_constant(self.variable) 708 elif const == NOT_CONST: 709 analysis.mark_not_constant(self.variable) 710 711 def prepare(self, analysis): 712 713 SLBlock.prepare(self, analysis) 714 715 # Prepare the positional arguments. 716 717 exprs = [ ] 718 values = [ ] 719 has_exprs = False 720 has_values = False 721 722 for a in self.positional: 723 node = ccache.ast_eval(a) 724 725 const = analysis.is_constant(node) 726 727 if const == GLOBAL_CONST: 728 values.append(py_eval_bytecode(compile_expr(self.location, node))) 729 exprs.append(ast.Num(n=0)) 730 has_values = True 731 else: 732 values.append(use_expression) 733 exprs.append(node) # Will be compiled as part of the tuple. 734 has_exprs = True 735 736 self.constant = min(self.constant, const) 737 738 if has_values: 739 self.positional_values = values 740 else: 741 self.positional_values = None 742 743 if has_exprs: 744 t = ast.Tuple(elts=exprs, ctx=ast.Load()) 745 ast.copy_location(t, exprs[0]) 746 self.positional_exprs = compile_expr(self.location, t) 747 else: 748 self.positional_exprs = None 749 750 # We do not pass keywords to our parents. 751 self.has_keyword = False 752 753 # We want to preserve last_keyword, however, in case we run a 754 # python block. 755 756 # If we have the id property, we're not constant - since we may get 757 # additional keywords via id. (It's unlikely, but id should be pretty 758 # rare.) 759 if self.keyword_exist("id"): 760 self.constant = NOT_CONST 761 762 if self.variable is not None: 763 self.constant = NOT_CONST 764 765 def keywords(self, context): 766 # We do not want to pass keywords to our parents, so just return. 767 return 768 769 def execute(self, context): 770 771 debug = context.debug 772 773 screen = renpy.ui.screen 774 775 cache = context.old_cache.get(self.serial, None) or context.miss_cache.get(self.serial, None) 776 777 if not isinstance(cache, SLCache): 778 cache = SLCache() 779 780 context.new_cache[self.serial] = cache 781 782 copy_on_change = cache.copy_on_change 783 784 if debug: 785 self.debug_line() 786 787 if cache.constant and (cache.style_prefix == context.style_prefix): 788 789 for i, local_scope, context_scope in cache.constant_uses_scope: 790 791 if context_scope is None: 792 context_scope = context.root_scope 793 794 if local_scope: 795 scope = dict(context_scope) 796 scope.update(local_scope) 797 else: 798 scope = context_scope 799 800 if copy_on_change: 801 if i._scope(scope, False): 802 cache.constant = None 803 break 804 else: 805 i._scope(scope, True) 806 807 else: 808 809 d = cache.constant 810 811 if d is not NO_DISPLAYABLE: 812 if context.showif is not None: 813 d = self.wrap_in_showif(d, context, cache) 814 815 context.children.append(d) 816 817 if context.uses_scope is not None: 818 context.uses_scope.extend(cache.constant_uses_scope) 819 820 if debug: 821 profile_log.write(" reused constant displayable") 822 823 return 824 825 # Create the context. 826 ctx = SLContext(context) 827 828 # True if we encountered an exception that we're recovering from 829 # due to being in prediction mode. 830 fail = False 831 832 # The main displayable we're predicting. 833 main = None 834 835 # True if we've pushed something onto the imagemap stack. 836 imagemap = False 837 838 try: 839 # Evaluate the positional arguments. 840 positional_values = self.positional_values 841 positional_exprs = self.positional_exprs 842 843 if positional_values and positional_exprs: 844 values = eval(positional_exprs, context.globals, context.scope) 845 positional = [ b if (a is use_expression) else a for a, b in zip(positional_values, values) ] 846 elif positional_values: 847 positional = positional_values 848 elif positional_exprs: 849 positional = eval(positional_exprs, context.globals, context.scope) 850 else: 851 positional = [ ] 852 853 keywords = ctx.keywords = self.default_keywords.copy() 854 855 if self.constant: 856 ctx.uses_scope = [ ] 857 858 SLBlock.keywords(self, ctx) 859 860 arguments = keywords.pop("arguments", None) 861 if arguments: 862 positional += arguments 863 864 properties = keywords.pop("properties", None) 865 if properties: 866 keywords.update(properties) 867 868 # Get the widget id and transform, if any. 869 widget_id = keywords.pop("id", None) 870 transform = keywords.pop("at", None) 871 872 # If we don't know the style, figure it out. 873 style_suffix = keywords.pop("style_suffix", None) or self.style 874 if ("style" not in keywords) and style_suffix: 875 if ctx.style_prefix is None: 876 keywords["style"] = style_suffix 877 else: 878 keywords["style"] = ctx.style_prefix + "_" + style_suffix 879 880 if widget_id and (widget_id in screen.widget_properties): 881 keywords.update(screen.widget_properties[widget_id]) 882 883 old_d = cache.displayable 884 if old_d: 885 old_main = old_d._main or old_d 886 else: 887 old_main = None 888 889 reused = False 890 891 if debug: 892 self.report_arguments(cache, positional, keywords, transform) 893 894 can_reuse = (old_d is not None) and (positional == cache.positional) and (keywords == cache.keywords) and (context.style_prefix == cache.style_prefix) 895 if (self.variable is not None) and copy_on_change: 896 can_reuse = False 897 898 # A hotspot can only be reused if the imagemap it belongs to has 899 # not changed. 900 if self.hotspot: 901 902 imc = renpy.ui.imagemap_stack[-1] 903 if cache.imagemap is not imc: 904 can_reuse = False 905 906 cache.imagemap = imc 907 908 if can_reuse: 909 reused = True 910 d = old_d 911 912 # The main displayable, if d is a composite displayable. (This is 913 # the one that gets the scope, and gets children added to it.) 914 main = old_main 915 916 if widget_id and not ctx.unlikely: 917 screen.widgets[widget_id] = main 918 screen.base_widgets[widget_id] = d 919 920 if self.scope and main._uses_scope: 921 if copy_on_change: 922 if main._scope(ctx.scope, False): 923 reused = False 924 else: 925 main._scope(ctx.scope, True) 926 927 if reused and self.imagemap: 928 imagemap = True 929 cache.imagemap.reuse() 930 renpy.ui.imagemap_stack.append(cache.imagemap) 931 932 if not reused: 933 cache.positional = positional 934 cache.keywords = keywords.copy() 935 936 # This child creation code is copied below, for the copy_on_change 937 # case. 938 if self.scope: 939 keywords["scope"] = ctx.scope 940 941 if self.replaces and ctx.updating: 942 keywords['replaces'] = old_main 943 944 # Pass the context 945 if self.pass_context: 946 keywords['context'] = ctx 947 948 d = self.displayable(*positional, **keywords) 949 main = d._main or d 950 951 main._location = self.location 952 953 if widget_id and not ctx.unlikely: 954 screen.widgets[widget_id] = main 955 screen.base_widgets[widget_id] = d 956 # End child creation code. 957 958 imagemap = self.imagemap 959 960 cache.copy_on_change = False # We no longer need to copy on change. 961 cache.children = None # Re-add the children. 962 963 if debug: 964 if reused: 965 profile_log.write(" reused displayable") 966 elif self.constant: 967 profile_log.write(" created constant displayable") 968 else: 969 profile_log.write(" created displayable") 970 971 except: 972 if not context.predicting: 973 raise 974 fail = True 975 976 if self.variable is not None: 977 context.scope[self.variable] = main 978 979 ctx.children = [ ] 980 ctx.showif = None 981 982 stack = renpy.ui.stack 983 stack.append(ctx) 984 985 try: 986 987 # Evaluate children. (Inlined SLBlock.execute) 988 for i in self.children: 989 try: 990 i.execute(ctx) 991 except: 992 if not context.predicting: 993 raise 994 fail = True 995 996 finally: 997 998 ctx.keywords = None 999 1000 stack.pop() 1001 1002 if imagemap: 1003 cache.imagemap = renpy.ui.imagemap_stack.pop() 1004 cache.imagemap.cache.finish() 1005 1006 # If a failure occurred during prediction, predict main (if known), 1007 # and ctx.children, and return. 1008 if fail: 1009 predict_displayable(main) 1010 1011 for i in ctx.children: 1012 predict_displayable(i) 1013 1014 context.fail = True 1015 1016 return 1017 1018 if ctx.children != cache.children: 1019 1020 if reused and copy_on_change: 1021 1022 # This is a copy of the child creation code from above. 1023 if self.scope: 1024 keywords["scope"] = ctx.scope 1025 1026 if self.replaces and context.updating: 1027 keywords['replaces'] = old_main 1028 1029 if self.pass_context: 1030 keywords['context'] = ctx 1031 1032 d = self.displayable(*positional, **keywords) 1033 main = d._main or d 1034 1035 main._location = self.location 1036 1037 if widget_id: 1038 screen.widgets[widget_id] = main 1039 screen.base_widgets[widget_id] = d 1040 # End child creation code. 1041 1042 cache.copy_on_change = False 1043 reused = False 1044 1045 if reused: 1046 main._clear() 1047 1048 if self.child_or_fixed and len(ctx.children) != 1: 1049 f = Fixed() 1050 1051 for i in ctx.children: 1052 f.add(i) 1053 1054 main.add(f) 1055 1056 else: 1057 for i in ctx.children: 1058 main.add(i) 1059 1060 # Inform the focus system about replacement displayables. 1061 if (not context.predicting) and (old_d is not None): 1062 replaced_by = renpy.display.focus.replaced_by 1063 replaced_by[id(old_d)] = d 1064 1065 if d is not main: 1066 for old_part, new_part in zip(old_d._composite_parts, d._composite_parts): 1067 replaced_by[id(old_part)] = new_part 1068 1069 cache.displayable = d 1070 cache.children = ctx.children 1071 cache.style_prefix = context.style_prefix 1072 1073 if (transform is not None) and (d is not NO_DISPLAYABLE): 1074 if reused and (transform == cache.raw_transform): 1075 1076 if isinstance(cache.inner_transform, renpy.display.transform.Transform): 1077 if cache.inner_transform.child is not d: 1078 cache.inner_transform.set_child(d, duplicate=False) 1079 1080 d = cache.outer_transform 1081 1082 else: 1083 old_outer_transform = cache.outer_transform 1084 1085 cache.raw_transform = transform 1086 cache.inner_transform = None 1087 cache.outer_transform = None 1088 1089 if isinstance(transform, Transform): 1090 d = transform(child=d) 1091 d._unique() 1092 1093 cache.inner_transform = d 1094 cache.outer_transform = d 1095 1096 elif isinstance(transform, list_or_tuple): 1097 for t in transform: 1098 if isinstance(t, Transform): 1099 d = t(child=d) 1100 1101 cache.outer_transform = d 1102 if cache.inner_transform is None: 1103 cache.inner_transform = d 1104 1105 else: 1106 d = t(d) 1107 cache.raw_transform = None 1108 cache.outer_transform = None 1109 cache.inner_transform = None 1110 1111 d._unique() 1112 1113 else: 1114 d = transform(d) 1115 d._unique() 1116 cache.raw_transform = None 1117 cache.outer_transform = None 1118 cache.inner_transform = None 1119 1120 if isinstance(d, Transform): 1121 1122 if not context.updating: 1123 old_outer_transform = None 1124 1125 d.take_state(old_outer_transform) 1126 d.take_execution_state(old_outer_transform) 1127 1128 else: 1129 cache.inner_transform = None 1130 cache.outer_transform = None 1131 cache.raw_transform = None 1132 1133 if ctx.fail: 1134 context.fail = True 1135 1136 else: 1137 if self.constant: 1138 cache.constant = d 1139 1140 if self.scope and main._uses_scope: 1141 1142 local_scope = { } 1143 1144 for i in self.local_constant: 1145 if i in ctx.scope: 1146 local_scope[i] = ctx.scope[i] 1147 1148 if ctx.scope is context.root_scope: 1149 ctx.uses_scope.append((main, local_scope, None)) 1150 else: 1151 ctx.uses_scope.append((main, local_scope, ctx.scope)) 1152 1153 cache.constant_uses_scope = ctx.uses_scope 1154 1155 if context.uses_scope is not None: 1156 context.uses_scope.extend(ctx.uses_scope) 1157 1158 if d is not NO_DISPLAYABLE: 1159 1160 if context.showif is not None: 1161 d = self.wrap_in_showif(d, context, cache) 1162 1163 context.children.append(d) 1164 1165 def wrap_in_showif(self, d, context, cache): 1166 """ 1167 Wraps `d` in a ShowIf displayable. 1168 """ 1169 1170 rv = renpy.sl2.sldisplayables.ShowIf(context.showif, cache.old_showif) 1171 rv.add(d) 1172 1173 if not context.predicting: 1174 cache.old_showif = rv 1175 1176 return rv 1177 1178 def report_arguments(self, cache, positional, keywords, transform): 1179 if positional: 1180 report = [ ] 1181 1182 values = self.positional_values or ([ use_expression ] * len(positional)) 1183 1184 for i in range(len(positional)): 1185 1186 if values[i] is not use_expression: 1187 report.append("const") 1188 elif cache.positional is None: 1189 report.append("new") 1190 elif cache.positional[i] == positional[i]: 1191 report.append("equal") 1192 else: 1193 report.append("not-equal") 1194 1195 profile_log.write(" args: %s", " ".join(report)) 1196 1197 values = self.keyword_values or { } 1198 1199 if keywords: 1200 report = { } 1201 1202 if cache.keywords is None: 1203 for k in keywords: 1204 1205 if k in values: 1206 report[k] = "const" 1207 continue 1208 1209 report[k] = "new" 1210 1211 else: 1212 for k in keywords: 1213 k = str(k) 1214 1215 if k in values: 1216 report[k] = "const" 1217 continue 1218 1219 if k not in cache.keywords: 1220 report[k] = "new-only" 1221 continue 1222 1223 if keywords[k] == cache.keywords[k]: 1224 report[k] = "equal" 1225 else: 1226 report[k] = "not-equal" 1227 1228 for k in cache.keywords: 1229 if k not in keywords: 1230 report[k] = "old-only" 1231 1232 profile_log.write(" kwargs: %r", report) 1233 1234 if transform is not None: 1235 if "at" in values: 1236 profile_log.write(" at: const") 1237 elif cache.raw_transform is None: 1238 profile_log.write(" at: new") 1239 elif cache.raw_transform == transform: 1240 profile_log.write(" at: equal") 1241 else: 1242 profile_log.write(" at: not-equal") 1243 1244 def copy_on_change(self, cache): 1245 c = cache.get(self.serial, None) 1246 1247 if isinstance(c, SLCache): 1248 c.copy_on_change = True 1249 1250 for i in self.children: 1251 i.copy_on_change(cache) 1252 1253 1254class SLIf(SLNode): 1255 """ 1256 A screen language AST node that corresponds to an If/Elif/Else statement. 1257 """ 1258 1259 def __init__(self, loc): 1260 """ 1261 An AST node that represents an if statement. 1262 """ 1263 SLNode.__init__(self, loc) 1264 1265 # A list of entries, with each consisting of an expression (or 1266 # None, for the else block) and a SLBlock. 1267 self.entries = [ ] 1268 1269 def copy(self, transclude): 1270 rv = self.instantiate(transclude) 1271 1272 rv.entries = [ (expr, block.copy(transclude)) for expr, block in self.entries ] 1273 1274 return rv 1275 1276 def analyze(self, analysis): 1277 1278 const = GLOBAL_CONST 1279 1280 for cond, _block in self.entries: 1281 if cond is not None: 1282 const = min(const, analysis.is_constant_expr(cond)) 1283 1284 analysis.push_control(const) 1285 1286 for _cond, block in self.entries: 1287 block.analyze(analysis) 1288 1289 analysis.pop_control() 1290 1291 def prepare(self, analysis): 1292 1293 # A list of prepared entries, with each consisting of expression 1294 # bytecode and a SLBlock. 1295 self.prepared_entries = [ ] 1296 1297 for cond, block in self.entries: 1298 if cond is not None: 1299 node = ccache.ast_eval(cond) 1300 1301 self.constant = min(self.constant, analysis.is_constant(node)) 1302 1303 cond = compile_expr(self.location, node) 1304 1305 block.prepare(analysis) 1306 self.constant = min(self.constant, block.constant) 1307 self.prepared_entries.append((cond, block)) 1308 1309 self.has_keyword |= block.has_keyword 1310 self.last_keyword |= block.last_keyword 1311 1312 def execute(self, context): 1313 1314 if context.predicting: 1315 self.execute_predicting(context) 1316 return 1317 1318 for cond, block in self.prepared_entries: 1319 if cond is None or eval(cond, context.globals, context.scope): 1320 for i in block.children: 1321 i.execute(context) 1322 return 1323 1324 def execute_predicting(self, context): 1325 # A variant of the this code that runs while predicting, executing 1326 # all paths of the if. 1327 1328 # True if no block has been the main choice yet. 1329 first = True 1330 1331 # Other blocks that we predict if not predicted. 1332 false_blocks = [ ] 1333 1334 for cond, block in self.prepared_entries: 1335 try: 1336 cond_value = (cond is None) or eval(cond, context.globals, context.scope) 1337 except: 1338 cond_value = False 1339 1340 # The taken branch. 1341 if first and cond_value: 1342 first = False 1343 1344 for i in block.children: 1345 try: 1346 i.execute(context) 1347 except: 1348 pass 1349 1350 else: 1351 false_blocks.append(block) 1352 1353 # Has any instance of this node been predicted? We only predict 1354 # once per node, for performance reasons. 1355 if self.serial in context.predicted: 1356 return 1357 1358 context.predicted.add(self.serial) 1359 1360 # Not-taken branches. 1361 for block in false_blocks: 1362 ctx = SLContext(context) 1363 ctx.children = [ ] 1364 ctx.unlikely = True 1365 1366 for i in block.children: 1367 try: 1368 i.execute(ctx) 1369 except: 1370 pass 1371 1372 for i in ctx.children: 1373 predict_displayable(i) 1374 1375 def keywords(self, context): 1376 1377 for cond, block in self.prepared_entries: 1378 if cond is None or eval(cond, context.globals, context.scope): 1379 block.keywords(context) 1380 return 1381 1382 def copy_on_change(self, cache): 1383 for _cond, block in self.entries: 1384 block.copy_on_change(cache) 1385 1386 def used_screens(self, callback): 1387 for _cond, block in self.entries: 1388 block.used_screens(callback) 1389 1390 def has_transclude(self): 1391 for _cond, block in self.entries: 1392 if block.has_transclude(): 1393 return True 1394 1395 return False 1396 1397 def has_python(self): 1398 return any(i[1].has_python() for i in self.entries) 1399 1400 def keyword_exist(self, name): 1401 return any(i[1].keyword_exist(name) for i in self.entries) 1402 1403 1404class SLShowIf(SLNode): 1405 """ 1406 The AST node that corresponds to the showif statement. 1407 """ 1408 1409 def __init__(self, loc): 1410 """ 1411 An AST node that represents an if statement. 1412 """ 1413 SLNode.__init__(self, loc) 1414 1415 # A list of entries, with each consisting of an expression (or 1416 # None, for the else block) and a SLBlock. 1417 self.entries = [ ] 1418 1419 def copy(self, transclude): 1420 rv = self.instantiate(transclude) 1421 1422 rv.entries = [ (expr, block.copy(transclude)) for expr, block in self.entries ] 1423 1424 return rv 1425 1426 def analyze(self, analysis): 1427 1428 for _cond, block in self.entries: 1429 block.analyze(analysis) 1430 1431 def prepare(self, analysis): 1432 1433 # A list of prepared entries, with each consisting of expression 1434 # bytecode and a SLBlock. 1435 self.prepared_entries = [ ] 1436 1437 for cond, block in self.entries: 1438 if cond is not None: 1439 node = ccache.ast_eval(cond) 1440 1441 self.constant = min(self.constant, analysis.is_constant(node)) 1442 1443 cond = compile_expr(self.location, node) 1444 1445 block.prepare(analysis) 1446 self.constant = min(self.constant, block.constant) 1447 self.prepared_entries.append((cond, block)) 1448 1449 self.last_keyword = True 1450 1451 def execute(self, context): 1452 1453 # This is true when the block should be executed - when no outer 1454 # showif is False, and when no prior block in this showif has 1455 # executed. 1456 first_true = context.showif is not False 1457 1458 for cond, block in self.prepared_entries: 1459 1460 ctx = SLContext(context) 1461 1462 if not first_true: 1463 ctx.showif = False 1464 else: 1465 if cond is None or eval(cond, context.globals, context.scope): 1466 ctx.showif = True 1467 first_true = False 1468 else: 1469 ctx.showif = False 1470 1471 for i in block.children: 1472 i.execute(ctx) 1473 1474 if ctx.fail: 1475 context.fail = True 1476 1477 def copy_on_change(self, cache): 1478 for _cond, block in self.entries: 1479 block.copy_on_change(cache) 1480 1481 def used_screens(self, callback): 1482 for _cond, block in self.entries: 1483 block.used_screens(callback) 1484 1485 def has_transclude(self): 1486 for _cond, block in self.entries: 1487 if block.has_transclude(): 1488 return True 1489 1490 return False 1491 1492 def has_python(self): 1493 return any(i[1].has_python() for i in self.entries) 1494 1495 1496class SLFor(SLBlock): 1497 """ 1498 The AST node that corresponds to a for statement. This only supports 1499 simple for loops that assign a single variable. 1500 """ 1501 1502 index_expression = None 1503 1504 def __init__(self, loc, variable, expression, index_expression): 1505 SLBlock.__init__(self, loc) 1506 1507 self.variable = variable 1508 self.expression = expression 1509 self.index_expression = index_expression 1510 1511 def copy(self, transclude): 1512 rv = self.instantiate(transclude) 1513 1514 rv.variable = self.variable 1515 rv.expression = self.expression 1516 rv.index_expression = self.index_expression 1517 1518 return rv 1519 1520 def analyze(self, analysis): 1521 1522 if analysis.is_constant_expr(self.expression) == GLOBAL_CONST: 1523 analysis.push_control(True) 1524 analysis.mark_constant(self.variable) 1525 else: 1526 analysis.push_control(False) 1527 analysis.mark_not_constant(self.variable) 1528 1529 SLBlock.analyze(self, analysis) 1530 1531 analysis.pop_control() 1532 1533 def prepare(self, analysis): 1534 node = ccache.ast_eval(self.expression) 1535 1536 const = analysis.is_constant(node) 1537 1538 if const == GLOBAL_CONST: 1539 self.expression_value = py_eval_bytecode(compile_expr(self.location, node)) 1540 self.expression_expr = None 1541 else: 1542 self.expression_value = None 1543 self.expression_expr = compile_expr(self.location, node) 1544 1545 self.constant = min(self.constant, const) 1546 1547 SLBlock.prepare(self, analysis) 1548 1549 self.last_keyword = True 1550 1551 def execute(self, context): 1552 1553 variable = self.variable 1554 expr = self.expression_expr 1555 1556 try: 1557 if expr is not None: 1558 value = eval(expr, context.globals, context.scope) 1559 else: 1560 value = self.expression_value 1561 except: 1562 if not context.predicting: 1563 raise 1564 1565 value = [ 0 ] 1566 1567 newcaches = { } 1568 1569 oldcaches = context.old_cache.get(self.serial, newcaches) or { } 1570 1571 if not isinstance(oldcaches, dict): 1572 oldcaches = { } 1573 1574 misscaches = context.miss_cache.get(self.serial, newcaches) or { } 1575 1576 if not isinstance(misscaches, dict): 1577 misscaches = { } 1578 1579 ctx = SLContext(context) 1580 1581 for index, v in enumerate(value): 1582 1583 ctx.scope[variable] = v 1584 1585 children_i = iter(self.children) 1586 1587 # If we have a variable expression as a tuple, it is necessary 1588 # to execute the first child before evaluating the index value, 1589 # because the index can be one of this tuple member. 1590 if variable == "_sl2_i": 1591 sl_python = next(children_i) 1592 # It can only fail if the unpacking fails, but it can still 1593 try: 1594 sl_python.execute(ctx) 1595 except: 1596 if not context.predicting: 1597 raise 1598 1599 if self.index_expression is not None: 1600 index = eval(self.index_expression, ctx.globals, ctx.scope) 1601 1602 ctx.old_cache = oldcaches.get(index, None) or { } 1603 1604 if not isinstance(ctx.old_cache, dict): 1605 ctx.old_cache = {} 1606 1607 ctx.miss_cache = misscaches.get(index, None) or { } 1608 1609 if not isinstance(ctx.miss_cache, dict): 1610 ctx.miss_cache = {} 1611 1612 newcaches[index] = ctx.new_cache = { } 1613 1614 # Inline of SLBlock.execute. 1615 1616 for i in children_i: 1617 try: 1618 i.execute(ctx) 1619 except: 1620 if not context.predicting: 1621 raise 1622 1623 if context.unlikely: 1624 break 1625 1626 context.new_cache[self.serial] = newcaches 1627 1628 if ctx.fail: 1629 context.fail = True 1630 1631 def keywords(self, context): 1632 return 1633 1634 def copy_on_change(self, cache): 1635 c = cache.get(self.serial, None) 1636 1637 if not isinstance(c, dict): 1638 return 1639 1640 for child_cache in c.values(): 1641 for i in self.children: 1642 i.copy_on_change(child_cache) 1643 1644 1645class SLPython(SLNode): 1646 1647 def __init__(self, loc, code): 1648 SLNode.__init__(self, loc) 1649 1650 # A pycode object. 1651 self.code = code 1652 1653 def copy(self, transclude): 1654 rv = self.instantiate(transclude) 1655 1656 rv.code = self.code 1657 1658 return rv 1659 1660 def analyze(self, analysis): 1661 analysis.python(self.code.source) 1662 1663 def execute(self, context): 1664 exec(self.code.bytecode, context.globals, context.scope) 1665 1666 def prepare(self, analysis): 1667 self.constant = NOT_CONST 1668 self.last_keyword = True 1669 1670 def has_python(self): 1671 return True 1672 1673 1674class SLPass(SLNode): 1675 1676 def execute(self, context): 1677 return 1678 1679 def copy(self, transclude): 1680 rv = self.instantiate(transclude) 1681 1682 return rv 1683 1684 1685class SLDefault(SLNode): 1686 1687 def __init__(self, loc, variable, expression): 1688 SLNode.__init__(self, loc) 1689 1690 self.variable = variable 1691 self.expression = expression 1692 1693 def copy(self, transclude): 1694 rv = self.instantiate(transclude) 1695 1696 rv.variable = self.variable 1697 rv.expression = self.expression 1698 1699 return rv 1700 1701 def analyze(self, analysis): 1702 analysis.mark_not_constant(self.variable) 1703 1704 def prepare(self, analysis): 1705 self.expr = compile_expr(self.location, ccache.ast_eval(self.expression)) 1706 self.constant = NOT_CONST 1707 self.last_keyword = True 1708 1709 def execute(self, context): 1710 scope = context.scope 1711 variable = self.variable 1712 1713 if variable in scope: 1714 return 1715 1716 scope[variable] = eval(self.expr, context.globals, scope) 1717 1718 def has_python(self): 1719 return True 1720 1721 1722class SLUse(SLNode): 1723 1724 id = None 1725 block = None 1726 1727 def __init__(self, loc, target, args, id_expr, block): 1728 1729 SLNode.__init__(self, loc) 1730 1731 # The name of the screen we're accessing. 1732 self.target = target 1733 1734 # If the target is an SL2 screen, the SLScreen node at the root of 1735 # the ast for that screen. 1736 self.ast = None 1737 1738 # If arguments are given, those arguments. 1739 self.args = args 1740 1741 # An expression, if the id property is given. 1742 self.id = id_expr 1743 1744 # A block for transclusion, or None if the statement does not have a 1745 # block. 1746 self.block = block 1747 1748 def copy(self, transclude): 1749 1750 rv = self.instantiate(transclude) 1751 1752 rv.target = self.target 1753 rv.args = self.args 1754 rv.id = self.id 1755 1756 if self.block is not None: 1757 rv.block = self.block.copy(transclude) 1758 else: 1759 rv.block = None 1760 1761 rv.ast = None 1762 1763 return rv 1764 1765 def analyze(self, analysis): 1766 1767 self.last_keyword = True 1768 1769 if self.id: 1770 self.constant = NOT_CONST 1771 1772 if self.block: 1773 self.block.analyze(analysis) 1774 1775 def prepare(self, analysis): 1776 1777 self.ast = None 1778 1779 if self.block: 1780 self.block.prepare(analysis) 1781 1782 if self.block.constant == GLOBAL_CONST: 1783 const = True 1784 else: 1785 const = False 1786 else: 1787 const = True 1788 1789 if isinstance(self.target, renpy.ast.PyExpr): 1790 1791 self.constant = NOT_CONST 1792 const = False 1793 self.ast = None 1794 1795 else: 1796 1797 target = renpy.display.screen.get_screen_variant(self.target) 1798 1799 if target is None: 1800 self.constant = NOT_CONST 1801 1802 if renpy.config.developer: 1803 raise Exception("A screen named {} does not exist.".format(self.target)) 1804 else: 1805 return 1806 1807 if target.ast is None: 1808 self.constant = NOT_CONST 1809 return 1810 1811 if const: 1812 self.ast = target.ast.const_ast 1813 else: 1814 self.ast = target.ast.not_const_ast 1815 1816 self.constant = min(self.constant, self.ast.constant) 1817 1818 def execute_use_screen(self, context): 1819 1820 # Create an old-style displayable name for this call site. 1821 serial = context.use_index[self.serial] 1822 context.use_index[self.serial] = serial + 1 1823 1824 name = ( 1825 context.scope.get("_name", ()), 1826 self.serial, 1827 serial) 1828 1829 if self.args: 1830 args, kwargs = self.args.evaluate(context.scope) 1831 else: 1832 args = [ ] 1833 kwargs = { } 1834 1835 renpy.display.screen.use_screen(self.target, _name=name, _scope=context.scope, *args, **kwargs) 1836 1837 def execute(self, context): 1838 1839 if isinstance(self.target, renpy.ast.PyExpr): 1840 target_name = eval(self.target, context.globals, context.scope) 1841 target = renpy.display.screen.get_screen_variant(target_name) 1842 1843 if target is None: 1844 raise Exception("A screen named {} does not exist.".format(target_name)) 1845 1846 ast = target.ast.not_const_ast 1847 1848 id_prefix = "_use_expression" 1849 1850 else: 1851 id_prefix = self.target 1852 ast = self.ast 1853 1854 # If self.ast is not an SL2 screen, run it using renpy.display.screen.use_screen. 1855 if ast is None: 1856 self.execute_use_screen(context) 1857 return 1858 1859 # Otherwise, run the use statement directly. 1860 1861 # Figure out the cache to use. 1862 1863 ctx = SLContext(context) 1864 ctx.new_cache = context.new_cache[self.serial] = { "ast" : ast } 1865 ctx.miss_cache = context.miss_cache.get(self.serial, None) or { } 1866 1867 if self.id: 1868 1869 use_id = (id_prefix, eval(self.id, context.globals, context.scope)) 1870 1871 ctx.old_cache = context.old_use_cache.get(use_id, None) or context.old_cache.get(self.serial, None) or { } 1872 1873 if use_id in ctx.old_use_cache: 1874 ctx.updating = True 1875 1876 ctx.new_use_cache[use_id] = ctx.new_cache 1877 1878 else: 1879 1880 ctx.old_cache = context.old_cache.get(self.serial, None) or { } 1881 1882 if not isinstance(ctx.old_cache, dict): 1883 ctx.old_cache = { } 1884 if not isinstance(ctx.miss_cache, dict): 1885 ctx.miss_cache = { } 1886 1887 # Evaluate the arguments. 1888 try: 1889 if self.args: 1890 args, kwargs = self.args.evaluate(context.scope) 1891 else: 1892 args = [ ] 1893 kwargs = { } 1894 except: 1895 if not context.predicting: 1896 raise 1897 1898 args = [ ] 1899 kwargs = { } 1900 1901 # Apply the arguments to the parameters (if present) or to the scope of the used screen. 1902 if ast.parameters is not None: 1903 new_scope = ast.parameters.apply(args, kwargs, ignore_errors=context.predicting) 1904 1905 scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { } 1906 scope.update(new_scope) 1907 1908 else: 1909 1910 if args: 1911 raise Exception("Screen {} does not take positional arguments. ({} given)".format(self.target, len(args))) 1912 1913 scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { } 1914 scope.clear() 1915 scope.update(context.scope) 1916 scope.update(kwargs) 1917 1918 scope["_scope"] = scope 1919 ctx.new_cache["scope"] = scope 1920 1921 # Run the child screen. 1922 ctx.scope = scope 1923 ctx.parent = weakref.ref(context) 1924 1925 ctx.transclude = self.block 1926 1927 try: 1928 ast.execute(ctx) 1929 finally: 1930 del scope["_scope"] 1931 1932 if ctx.fail: 1933 context.fail = True 1934 1935 def copy_on_change(self, cache): 1936 1937 c = cache.get(self.serial, None) 1938 1939 if c is None: 1940 return 1941 1942 ast = c.get("ast", None) 1943 1944 if ast is not None: 1945 ast.copy_on_change(c) 1946 1947 def used_screens(self, callback): 1948 if not isinstance(self.target, renpy.ast.PyExpr): 1949 callback(self.target) 1950 1951 def has_transclude(self): 1952 if self.block: 1953 return self.block.has_transclude() 1954 else: 1955 return False 1956 1957 1958class SLTransclude(SLNode): 1959 1960 def __init__(self, loc): 1961 SLNode.__init__(self, loc) 1962 1963 def copy(self, transclude): 1964 rv = self.instantiate(transclude) 1965 rv.constant = transclude 1966 return rv 1967 1968 def execute(self, context): 1969 1970 if not context.transclude: 1971 return 1972 1973 parent = context.parent 1974 if parent is not None: 1975 parent = parent() 1976 1977 ctx = SLContext(parent) 1978 ctx.new_cache = context.new_cache[self.serial] = { } 1979 ctx.old_cache = context.old_cache.get(self.serial, None) or { } 1980 ctx.miss_cache = context.miss_cache.get(self.serial, None) or { } 1981 ctx.uses_scope = context.uses_scope 1982 1983 if not isinstance(ctx.old_cache, dict): 1984 ctx.old_cache = { } 1985 if not isinstance(ctx.miss_cache, dict): 1986 ctx.miss_cache = { } 1987 1988 ctx.new_cache["transclude"] = context.transclude 1989 1990 ctx.children = context.children 1991 ctx.showif = context.showif 1992 1993 try: 1994 renpy.ui.stack.append(ctx) 1995 context.transclude.keywords(ctx) 1996 context.transclude.execute(ctx) 1997 finally: 1998 renpy.ui.stack.pop() 1999 2000 if ctx.fail: 2001 context.fail = True 2002 2003 def copy_on_change(self, cache): 2004 2005 c = cache.get(self.serial, None) 2006 2007 if c is None or "transclude" not in c: 2008 return 2009 2010 SLBlock.copy_on_change(c["transclude"], c) 2011 2012 def has_transclude(self): 2013 return True 2014 2015 2016class SLCustomUse(SLNode): 2017 """This represents special use screen statement defined 2018 by renpy.register_sl_statement. 2019 """ 2020 2021 def __init__(self, loc, target, positional, block): 2022 2023 SLNode.__init__(self, loc) 2024 2025 # The name of the screen we're accessing. 2026 self.target = target 2027 2028 # The SL2 SLScreen node at the root of the ast for that screen. 2029 self.ast = None 2030 2031 # Positional argument expressions. 2032 self.positional = positional 2033 2034 # A block for transclusion, from which we also take kwargs. 2035 self.block = block 2036 2037 def copy(self, transclude): 2038 2039 rv = self.instantiate(transclude) 2040 2041 rv.target = self.target 2042 rv.ast = None 2043 2044 rv.positional = self.positional 2045 rv.block = self.block.copy(transclude) 2046 2047 return rv 2048 2049 def analyze(self, analysis): 2050 2051 self.last_keyword = True 2052 2053 self.block.analyze(analysis) 2054 2055 def prepare(self, analysis): 2056 2057 block = self.block 2058 2059 block.prepare(analysis) 2060 2061 # Figure out the ast we want to use. 2062 target = renpy.display.screen.get_screen_variant(self.target) 2063 2064 if target is None: 2065 self.constant = NOT_CONST 2066 2067 if renpy.config.developer: 2068 raise Exception("A screen named {} does not exist.".format(self.target)) 2069 else: 2070 return 2071 2072 if target.ast is None: 2073 self.constant = NOT_CONST 2074 2075 if renpy.config.developer: 2076 raise Exception("A screen used in CD SLS should be a SL-based screen.") 2077 else: 2078 return 2079 2080 # If we have the id property, we're not constant - since we may get 2081 # our state via other screen on replace. 2082 if block.keyword_exist("id"): 2083 self.constant = NOT_CONST 2084 self.ast = target.ast.not_const_ast 2085 2086 elif block.constant == GLOBAL_CONST: 2087 self.ast = target.ast.const_ast 2088 else: 2089 self.ast = target.ast.not_const_ast 2090 2091 self.constant = min(self.constant, self.ast.constant) 2092 2093 def execute(self, context): 2094 2095 # Figure out the cache to use. 2096 ctx = SLContext(context) 2097 ctx.new_cache = context.new_cache[self.serial] = { } 2098 ctx.miss_cache = context.miss_cache.get(self.serial, None) or { } 2099 2100 # Evaluate the arguments to use in screen. 2101 try: 2102 args = [eval(i, context.globals, context.scope) for i in self.positional] 2103 2104 kwargs = ctx.keywords = {} 2105 2106 self.block.keywords(ctx) 2107 2108 arguments = kwargs.pop("arguments", None) 2109 if arguments: 2110 args += arguments 2111 2112 properties = kwargs.pop("properties", None) 2113 if properties: 2114 kwargs.update(properties) 2115 2116 # If we don't know the style, figure it out. 2117 style_suffix = kwargs.pop("style_suffix", None) 2118 if ("style" not in kwargs) and style_suffix: 2119 if ctx.style_prefix is None: 2120 kwargs["style"] = style_suffix 2121 else: 2122 kwargs["style"] = ctx.style_prefix + "_" + style_suffix 2123 2124 except: 2125 if not context.predicting: 2126 raise 2127 2128 args = [ ] 2129 kwargs = { } 2130 2131 # Get the id and deal with replacement algorithm. 2132 id = kwargs.pop("id", None) 2133 if id is not None: 2134 2135 use_id = (self.target, id) 2136 2137 ctx.old_cache = context.old_use_cache.get(use_id, None) or context.old_cache.get(self.serial, None) or { } 2138 2139 if use_id in ctx.old_use_cache: 2140 ctx.updating = True 2141 2142 ctx.new_use_cache[use_id] = ctx.new_cache 2143 2144 else: 2145 2146 ctx.old_cache = context.old_cache.get(self.serial, None) or { } 2147 2148 if not isinstance(ctx.old_cache, dict): 2149 ctx.old_cache = { } 2150 if not isinstance(ctx.miss_cache, dict): 2151 ctx.miss_cache = { } 2152 2153 ast = self.ast 2154 2155 # Apply the arguments to the parameters (if present) or to the scope of the used screen. 2156 if ast.parameters is not None: 2157 new_scope = ast.parameters.apply(args, kwargs, ignore_errors=context.predicting) 2158 2159 scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { } 2160 scope.update(new_scope) 2161 2162 else: 2163 2164 if args: 2165 raise Exception("Screen {} does not take positional arguments. ({} given)".format(self.target, len(args))) 2166 2167 scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { } 2168 scope.clear() 2169 scope.update(context.scope) 2170 scope.update(kwargs) 2171 2172 scope["_scope"] = scope 2173 ctx.new_cache["scope"] = scope 2174 2175 # Run the child screen. 2176 ctx.scope = scope 2177 ctx.parent = weakref.ref(context) 2178 2179 # If we have any children, pass them to (possible) transclude 2180 if self.block.children: 2181 ctx.transclude = self.block 2182 2183 try: 2184 ast.execute(ctx) 2185 finally: 2186 del scope["_scope"] 2187 2188 if ctx.fail: 2189 context.fail = True 2190 2191 def copy_on_change(self, cache): 2192 2193 c = cache.get(self.serial, None) 2194 if c is None: 2195 return 2196 2197 self.ast.copy_on_change(c) 2198 2199 def used_screens(self, callback): 2200 callback(self.target) 2201 2202 def has_transclude(self): 2203 return self.block.has_transclude() 2204 2205 2206class SLScreen(SLBlock): 2207 """ 2208 This represents a screen defined in the screen language 2. 2209 """ 2210 2211 version = 0 2212 2213 # This screen's AST when the transcluded block is entirely 2214 # constant (or there is no transcluded block at all). This may be 2215 # the actual AST, or a copy. 2216 const_ast = None 2217 2218 # A copy of this screen's AST when the transcluded block is not 2219 # constant. 2220 not_const_ast = None 2221 2222 # The analysis 2223 analysis = None 2224 2225 layer = "'screens'" 2226 sensitive = "True" 2227 2228 def __init__(self, loc): 2229 2230 SLBlock.__init__(self, loc) 2231 2232 # The name of the screen. 2233 self.name = None 2234 2235 # Should this screen be declared as modal? 2236 self.modal = "False" 2237 2238 # The screen's zorder. 2239 self.zorder = "0" 2240 2241 # The screen's tag. 2242 self.tag = None 2243 2244 # The variant of screen we're defining. 2245 self.variant = "None" # expr. 2246 2247 # Should we predict this screen? 2248 self.predict = "None" # expr. 2249 2250 # Should this screen be sensitive. 2251 self.sensitive = "True" 2252 2253 # The parameters this screen takes. 2254 self.parameters = None 2255 2256 # The analysis object used for this screen, if the screen has 2257 # already been analyzed. 2258 self.analysis = None 2259 2260 # True if this screen has been prepared. 2261 self.prepared = False 2262 2263 def copy(self, transclude): 2264 rv = self.instantiate(transclude) 2265 2266 rv.name = self.name 2267 rv.modal = self.modal 2268 rv.zorder = self.zorder 2269 rv.tag = self.tag 2270 rv.variant = self.variant 2271 rv.predict = self.predict 2272 rv.parameters = self.parameters 2273 rv.sensitive = self.sensitive 2274 2275 rv.prepared = False 2276 rv.analysis = None 2277 2278 return rv 2279 2280 def define(self, location): 2281 """ 2282 Defines a screen. 2283 """ 2284 2285 renpy.display.screen.define_screen( 2286 self.name, 2287 self, 2288 modal=self.modal, 2289 zorder=self.zorder, 2290 tag=self.tag, 2291 variant=renpy.python.py_eval(self.variant), 2292 predict=renpy.python.py_eval(self.predict), 2293 parameters=self.parameters, 2294 location=self.location, 2295 layer=renpy.python.py_eval(self.layer), 2296 sensitive=self.sensitive, 2297 ) 2298 2299 def analyze(self, analysis): 2300 2301 SLBlock.analyze(self, analysis) 2302 2303 def analyze_screen(self): 2304 2305 # Have we already been analyzed? 2306 if self.const_ast: 2307 return 2308 2309 key = (self.name, self.variant, self.location) 2310 2311 if key in scache.const_analyzed: 2312 self.const_ast = scache.const_analyzed[key] 2313 self.not_const_ast = scache.not_const_analyzed[key] 2314 return 2315 2316 self.const_ast = self 2317 2318 if self.has_transclude(): 2319 self.not_const_ast = self.copy(NOT_CONST) 2320 self.not_const_ast.const_ast = self.not_const_ast 2321 targets = [ self.const_ast, self.not_const_ast ] 2322 else: 2323 self.not_const_ast = self.const_ast 2324 targets = [ self.const_ast ] 2325 2326 for ast in targets: 2327 analysis = ast.analysis = Analysis(None) 2328 2329 if ast.parameters: 2330 analysis.parameters(ast.parameters) 2331 2332 ast.analyze(analysis) 2333 2334 while not analysis.at_fixed_point(): 2335 ast.analyze(analysis) 2336 2337 scache.const_analyzed[key] = self.const_ast 2338 scache.not_const_analyzed[key] = self.not_const_ast 2339 scache.updated = True 2340 2341 def unprepare_screen(self): 2342 self.prepared = False 2343 2344 def prepare_screen(self): 2345 2346 if self.prepared: 2347 return 2348 2349 self.analyze_screen() 2350 2351 # This version ensures we're not using the cache from an old 2352 # version of the screen. 2353 self.version += 1 2354 2355 self.const_ast.prepare(self.const_ast.analysis) 2356 2357 if self.not_const_ast is not self.const_ast: 2358 self.not_const_ast.prepare(self.not_const_ast.analysis) 2359 2360 self.prepared = True 2361 2362 if renpy.display.screen.get_profile(self.name).const: 2363 profile_log.write("CONST ANALYSIS %s", self.name) 2364 2365 new_constants = [ i for i in self.const_ast.analysis.global_constant if i not in renpy.pyanalysis.constants ] 2366 new_constants.sort() 2367 profile_log.write(' global_const: %s', " ".join(new_constants)) 2368 2369 local_constants = list(self.const_ast.analysis.local_constant) 2370 local_constants.sort() 2371 profile_log.write(' local_const: %s', " ".join(local_constants)) 2372 2373 not_constants = list(self.const_ast.analysis.not_constant) 2374 not_constants.sort() 2375 profile_log.write(' not_const: %s', " ".join(not_constants)) 2376 2377 def execute(self, context): 2378 self.const_ast.keywords(context) 2379 SLBlock.execute(self.const_ast, context) 2380 2381 def report_traceback(self, name, last): 2382 if last: 2383 return None 2384 2385 if name == "__call__": 2386 return [ ] 2387 2388 return SLBlock.report_traceback(self, name, last) 2389 2390 def copy_on_change(self, cache): 2391 SLBlock.copy_on_change(self.const_ast, cache) 2392 2393 def __call__(self, *args, **kwargs): 2394 scope = kwargs["_scope"] 2395 debug = kwargs.get("_debug", False) 2396 2397 if self.parameters: 2398 2399 args = scope.get("_args", ()) 2400 kwargs = scope.get("_kwargs", { }) 2401 2402 values = renpy.ast.apply_arguments(self.parameters, args, kwargs, ignore_errors=renpy.display.predict.predicting) 2403 scope.update(values) 2404 2405 if not self.prepared: 2406 self.prepare_screen() 2407 2408 current_screen = renpy.display.screen.current_screen() 2409 2410 if current_screen.screen_name[0] in renpy.config.profile_screens: 2411 debug = True 2412 2413 context = SLContext() 2414 2415 context.scope = scope 2416 context.root_scope = scope 2417 context.globals = renpy.python.store_dicts["store"] 2418 context.debug = debug 2419 context.predicting = renpy.display.predict.predicting 2420 context.updating = (current_screen.phase == renpy.display.screen.UPDATE) 2421 2422 name = scope["_name"] 2423 2424 def get_cache(d): 2425 rv = d.get(name, None) 2426 2427 if (not isinstance(rv, dict)) or (rv.get("version", None) != self.version): 2428 rv = { "version" : self.version } 2429 d[name] = rv 2430 2431 return rv 2432 2433 context.old_cache = get_cache(current_screen.cache) 2434 context.miss_cache = get_cache(current_screen.miss_cache) 2435 context.new_cache = { "version" : self.version } 2436 2437 context.old_use_cache = current_screen.use_cache 2438 context.new_use_cache = { } 2439 2440 # This really executes self.const_ast. 2441 self.execute(context) 2442 2443 for i in context.children: 2444 renpy.ui.implicit_add(i) 2445 2446 current_screen.cache[name] = context.new_cache 2447 current_screen.use_cache = context.new_use_cache 2448 2449 2450class ScreenCache(object): 2451 2452 def __init__(self): 2453 self.version = 1 2454 2455 self.const_analyzed = { } 2456 self.not_const_analyzed = { } 2457 2458 self.updated = False 2459 2460 2461scache = ScreenCache() 2462 2463CACHE_FILENAME = "cache/screens.rpyb" 2464 2465 2466def load_cache(): 2467 if renpy.game.args.compile: # @UndefinedVariable 2468 return 2469 2470 try: 2471 with renpy.loader.load(CACHE_FILENAME) as f: 2472 digest = f.read(hashlib.md5().digest_size) 2473 if digest != renpy.game.script.digest.digest(): 2474 return 2475 2476 s = loads(zlib.decompress(f.read())) 2477 2478 if s.version == scache.version: 2479 renpy.game.script.update_bytecode() 2480 scache.const_analyzed.update(s.const_analyzed) 2481 scache.not_const_analyzed.update(s.not_const_analyzed) 2482 2483 except: 2484 pass 2485 2486 2487def save_cache(): 2488 if not scache.updated: 2489 return 2490 2491 if renpy.macapp: 2492 return 2493 2494 try: 2495 data = zlib.compress(dumps(scache, 2), 3) 2496 2497 with open(renpy.loader.get_path(CACHE_FILENAME), "wb") as f: 2498 f.write(renpy.game.script.digest.digest()) 2499 f.write(data) 2500 except: 2501 pass 2502