Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 902514c3e1 | |||
| 3ace1a1cbb | |||
| 8fdafb50ba | |||
| b6b6cfa9ea | |||
| d5416f8f86 | |||
| 88af74ab10 | |||
| a0a84714ce | |||
| 2155bb02ae | |||
| bc6e2a63d0 | |||
| 204ca35321 | |||
| 899307d791 | |||
| 0bf4ab51d6 | |||
| 88d3870d5c | |||
| db7d998c38 | |||
| b1623451e8 | |||
| af7314a8a1 | |||
| a8567d0811 | |||
| 74a6660ae1 | |||
| 797e37678a | |||
| c4528cf6ad | |||
| 1417e58963 | |||
| 65440785e5 | |||
| c8a2c7036f | |||
| c512985741 | |||
| a2d6911b4e | |||
| d59029d66c | |||
| a9ecae1a59 | |||
| 3bf1b78250 | |||
| 2eda0dc2e9 | |||
| 7084ee4b46 | |||
| 93d760c9fa | |||
| 01f74075b8 | |||
| 51f523b3c3 | |||
| faf1e15710 | |||
| ef4c0bd1d9 | |||
| 339300d2a0 | |||
| 51f6b0d756 | |||
| f547ea3ed1 | |||
| 8911825d46 | |||
| ddc3f67539 | |||
| 99915a3ed3 | |||
| 21774019a7 | |||
| 33fbe01388 | |||
| 82dd5968e3 | |||
| 43c6f83aff | |||
| e6fc6e3c26 | |||
| 7fe14c09c9 | |||
| 588824cf3f | |||
| ddf7b35160 | |||
| d85879b1fd | |||
| adfbb4970b | |||
| bbf2cff58f | |||
| a1a0b78cb4 | |||
| 4cd14e18b5 | |||
| e70011862a | |||
| 916c6cc9e9 | |||
| f1e2d074c6 | |||
| 6e83ae9bd1 | |||
| d3506085c8 | |||
| 02d7a3305a | |||
| cd590d78eb | |||
| 0ed2c16ee9 | |||
| fc429eb984 | |||
| 623c5f6d1f | |||
| b430ca2243 | |||
| 8e21bcbc00 | |||
| 31ff6d3e95 | |||
| 6aadfe4d15 | |||
| 435fec2be6 | |||
| 6b507d5a57 | |||
| 25b0068b1d | |||
| b1a3171a40 | |||
| 30af1dfb70 | |||
| fca89d813e | |||
| 9050a9d5a5 | |||
| d257b9dc70 | |||
| 89c79e55fe | |||
| d7f194072e | |||
| 60ca4cd5ae | |||
| 20c1f572d7 | |||
| a73e20288c | |||
| 16fe7290fa | |||
| ff04b3ea56 | |||
| 903e971395 | |||
| 45e942de05 | |||
| 24a699ba12 | |||
| f5ed3d7947 | |||
| c761ba4c75 | |||
| 79fbc7718e | |||
| 7d4f7d17f9 | |||
| 439bcf73f3 | |||
| dc81a5edeb | |||
| 331a4f0211 | |||
| 69f1a2bb0b | |||
| cd90dfa789 | |||
| d20d9f2aa7 | |||
| fb1d40db3c | |||
| 4f835f1250 | |||
| 0843e46e91 | |||
| a0963c18ee | |||
| f0dfe18bbf | |||
| 9c6cc41ec3 | |||
| 41e13911e3 | |||
| 35f6794db2 | |||
| 699c2a1679 | |||
| cad9dc37c2 | |||
| deb959a1e7 | |||
| 93c4c4e8df | |||
| c634ff91d2 | |||
| 5b0f4cf5a7 | |||
| ddd0896840 | |||
| a175edde26 | |||
| 4ebedb6a20 | |||
| d3c827fc55 | |||
| fc9744192d | |||
| 72602ab6ab | |||
| b78f1d1b41 | |||
| e8bbfde5b1 | |||
| 79bda8a568 | |||
| 1fefbeba52 | |||
| 1336b5cfb8 | |||
| 935a25490a | |||
| 5f73d6f1b2 | |||
| 595e4d45bd | |||
| c42667b197 | |||
| 00750cb5d5 | |||
| 5f42c6bff9 | |||
| e6e11dab9f | |||
| 6351d60213 | |||
| 2928e5495c | |||
| 0b5a35a208 | |||
| ffd5eddaf9 | |||
| 27b4c46013 | |||
| c8da44781d | |||
| acf2347930 | |||
| a40d302500 | |||
| 8ad44080ae | |||
| c41b740ae6 | |||
| 67e508c979 | |||
| a7e9d39c1d | |||
| 921ddb07f7 | |||
| b2b222f23e | |||
| af202584db | |||
| d38dbc79b2 | |||
| 15c6fdf8c1 | |||
| 5e02e88ec0 | |||
| 3337049968 | |||
| 2565157a5a | |||
| 5b0cd0bce7 | |||
| 2901314d05 | |||
| 9ec1cd3c4f | |||
| b120f82f98 | |||
| c213d17436 |
+2
-2
@@ -71,14 +71,14 @@ To return to this virtualenv later, run::
|
||||
|
||||
After activating the virtualenv, install additional dependencies::
|
||||
|
||||
pip install -U cython future six typing pefile requests ecdsa
|
||||
pip install -U "setuptools cython<3.0.0 future six typing pefile requests ecdsa"
|
||||
|
||||
Then, install pygame_sdl2 by running the following commands::
|
||||
|
||||
git clone https://www.github.com/renpy/pygame_sdl2
|
||||
pushd pygame_sdl2
|
||||
python setup.py install
|
||||
python setup.py install_headers
|
||||
python install_headers.py $VIRTUAL_ENV
|
||||
popd
|
||||
|
||||
Next, set RENPY_DEPS_INSTALL To a \:-separated (\;-separated on Windows)
|
||||
|
||||
@@ -76,24 +76,14 @@ class BinFile(object):
|
||||
self.addr = addr
|
||||
|
||||
def tostring(self):
|
||||
if PY2:
|
||||
return self.a.tostring() # type: ignore
|
||||
else:
|
||||
return self.a.tobytes()
|
||||
return self.a.tobytes()
|
||||
|
||||
def substring(self, start, len): # @ReservedAssignment
|
||||
if PY2:
|
||||
return self.a[start:start + len].tostring() # type: ignore
|
||||
else:
|
||||
return self.a[start:start + len].tobytes()
|
||||
return self.a[start:start + len].tobytes()
|
||||
|
||||
def __init__(self, data):
|
||||
self.a = array.array('B')
|
||||
|
||||
if PY2:
|
||||
self.a.fromstring(data) # type: ignore
|
||||
else:
|
||||
self.a.frombytes(data)
|
||||
self.a.frombytes(data)
|
||||
|
||||
##############################################################################
|
||||
# These functions parse data out of the file. In these functions, offset is
|
||||
|
||||
@@ -432,10 +432,7 @@ fix_dlc("renios", "renios")
|
||||
for f in sorted(self, key=lambda a : a.name):
|
||||
f.hash(sha, distributor)
|
||||
|
||||
if PY2:
|
||||
return sha.hexdigest().decode("utf-8")
|
||||
else:
|
||||
return sha.hexdigest()
|
||||
return sha.hexdigest()
|
||||
|
||||
def split_by_prefix(self, prefix):
|
||||
"""
|
||||
@@ -661,7 +658,6 @@ fix_dlc("renios", "renios")
|
||||
# Build the mac app and windows exes.
|
||||
self.add_mac_files()
|
||||
self.add_windows_files()
|
||||
self.add_main_py()
|
||||
|
||||
# Add the main.py.
|
||||
self.add_main_py()
|
||||
@@ -1075,11 +1071,8 @@ fix_dlc("renios", "renios")
|
||||
|
||||
rv = self.temp_filename("Info.plist")
|
||||
|
||||
if PY2:
|
||||
plistlib.writePlist(plist, rv)
|
||||
else:
|
||||
with open(rv, "wb") as f:
|
||||
plistlib.dump(plist, f)
|
||||
with open(rv, "wb") as f:
|
||||
plistlib.dump(plist, f)
|
||||
|
||||
return rv
|
||||
|
||||
@@ -1114,16 +1107,6 @@ fix_dlc("renios", "renios")
|
||||
os.path.join(config.renpy_base, prefix + "linux-x86_64/renpy"),
|
||||
True)
|
||||
|
||||
armfn = os.path.join(config.renpy_base, prefix + "linux-armv7l/renpy")
|
||||
|
||||
if os.path.exists(armfn):
|
||||
|
||||
self.add_file(
|
||||
raspi,
|
||||
prefix + "linux-armv7l/" + self.executable_name,
|
||||
armfn,
|
||||
True)
|
||||
|
||||
aarch64fn = os.path.join(config.renpy_base, prefix + "linux-aarch64/renpy")
|
||||
|
||||
if os.path.exists(aarch64fn):
|
||||
@@ -1226,19 +1209,8 @@ fix_dlc("renios", "renios")
|
||||
if os.path.exists(tmp):
|
||||
self.add_file(fl, dst, tmp)
|
||||
|
||||
if PY2:
|
||||
|
||||
if self.build["include_i686"]:
|
||||
write_exe("lib/py2-windows-i686/renpy.exe", self.exe32, self.exe32, windows_i686)
|
||||
write_exe("lib/py2-windows-i686/pythonw.exe", "lib/py2-windows-i686/pythonw.exe", "pythonw-32.exe", windows_i686)
|
||||
|
||||
write_exe("lib/py2-windows-x86_64/renpy.exe", self.exe, self.exe, windows)
|
||||
write_exe("lib/py2-windows-x86_64/pythonw.exe", "lib/py2-windows-x86_64/pythonw.exe", "pythonw-64.exe", windows)
|
||||
|
||||
else:
|
||||
|
||||
write_exe("lib/py3-windows-x86_64/renpy.exe", self.exe, self.exe, windows)
|
||||
write_exe("lib/py3-windows-x86_64/pythonw.exe", "lib/py3-windows-x86_64/pythonw.exe", "pythonw-64.exe", windows)
|
||||
write_exe("lib/py3-windows-x86_64/renpy.exe", self.exe, self.exe, windows)
|
||||
write_exe("lib/py3-windows-x86_64/pythonw.exe", "lib/py3-windows-x86_64/pythonw.exe", "pythonw-64.exe", windows)
|
||||
|
||||
|
||||
def add_main_py(self):
|
||||
@@ -1536,7 +1508,7 @@ fix_dlc("renios", "renios")
|
||||
|
||||
if self.include_update and not format.startswith("app-"):
|
||||
|
||||
with open(update_fn, "wb" if PY2 else "w") as f:
|
||||
with open(update_fn, "w") as f:
|
||||
json.dump(update, f, indent=2)
|
||||
|
||||
if (not dlc) or (format == "update"):
|
||||
@@ -1572,11 +1544,6 @@ fix_dlc("renios", "renios")
|
||||
if os.path.exists(update_fn):
|
||||
shutil.copy(update_fn, final_update_fn)
|
||||
|
||||
if not directory:
|
||||
file_hash = hash_file(path)
|
||||
else:
|
||||
file_hash = ""
|
||||
|
||||
if format == "tar.bz2" or format == "bare-tar.bz2":
|
||||
pkg = TarPackage(path, "w:bz2")
|
||||
elif format == "update":
|
||||
|
||||
@@ -158,12 +158,8 @@ screen front_page_project:
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton "game" action OpenDirectory(os.path.join(p.path, "game"), absolute=True)
|
||||
textbutton "base" action OpenDirectory(os.path.join(p.path, "."), absolute=True)
|
||||
textbutton "images" action OpenDirectory(os.path.join(p.path, "game/images"), absolute=True)
|
||||
textbutton "audio" action OpenDirectory(os.path.join(p.path, "game/audio"), absolute=True)
|
||||
textbutton "gui" action OpenDirectory(os.path.join(p.path, "game/gui"), absolute=True)
|
||||
for button_name, path in p.data["renpy_launcher"]["open_directory"].items():
|
||||
textbutton button_name action OpenDirectory(os.path.join(p.path, path), absolute=True)
|
||||
|
||||
vbox:
|
||||
if persistent.show_edit_funcs:
|
||||
@@ -173,10 +169,8 @@ screen front_page_project:
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton "script.rpy" action editor.Edit("game/script.rpy", check=True)
|
||||
textbutton "options.rpy" action editor.Edit("game/options.rpy", check=True)
|
||||
textbutton "gui.rpy" action editor.Edit("game/gui.rpy", check=True)
|
||||
textbutton "screens.rpy" action editor.Edit("game/screens.rpy", check=True)
|
||||
for button_name, path in p.data["renpy_launcher"]["edit_file"].items():
|
||||
textbutton button_name action editor.Edit(path, check=True)
|
||||
|
||||
if editor.CanEditProject():
|
||||
textbutton _("Open project") action editor.EditProject()
|
||||
|
||||
@@ -172,7 +172,7 @@ class CodeGenerator(object):
|
||||
|
||||
for l in self.lines:
|
||||
|
||||
m = re.match('^(\s*)define (.*?) =', l)
|
||||
m = re.match(r'^(\s*)define (.*?) =', l)
|
||||
|
||||
if m:
|
||||
indent = m.group(1)
|
||||
|
||||
@@ -81,10 +81,7 @@ init python:
|
||||
|
||||
label install_live2d:
|
||||
python hide:
|
||||
if PY2:
|
||||
_prefix = r"lib/py2-"
|
||||
else:
|
||||
_prefix = r"lib/py3-"
|
||||
_prefix = r"lib/py3-"
|
||||
|
||||
patterns = [
|
||||
(r".*/Core/dll/linux/x86_64/(libLive2DCubismCore.so)", _prefix + r"linux-x86_64/\1"),
|
||||
@@ -97,11 +94,6 @@ label install_live2d:
|
||||
(r".*/Core/dll/android/(x86_64/libLive2DCubismCore.so)", r"rapt/prototype/renpyandroid/src/main/jniLibs/\1"),
|
||||
]
|
||||
|
||||
if PY2:
|
||||
patterns.extend([
|
||||
(r".*/Core/dll/windows/x86/(Live2DCubismCore.dll)", _prefix + r"windows-i686/\1"),
|
||||
])
|
||||
|
||||
install_from_zip("Live2D Cubism SDK for Native", "CubismSdkForNative-[45]-*.zip", patterns)
|
||||
|
||||
jump front_page
|
||||
|
||||
@@ -164,9 +164,6 @@ init python:
|
||||
|
||||
print("Eliminating __pycache__...")
|
||||
|
||||
if PY2:
|
||||
return
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
|
||||
@@ -34,9 +34,6 @@ label new_project:
|
||||
if persistent.projects_directory is None:
|
||||
$ interface.error(_("The projects directory could not be set. Giving up."))
|
||||
|
||||
if PY2:
|
||||
$ interface.info(_("Warning : you are using Ren'Py 7. It is recommended to start new projects using Ren'Py 8 instead."))
|
||||
|
||||
python:
|
||||
new_project_language = __("{#language name and font}")
|
||||
gui_kind = "new_gui_project"
|
||||
|
||||
+15
-13
@@ -322,7 +322,7 @@ init python:
|
||||
"""
|
||||
|
||||
if py is True:
|
||||
py = 'pyo' if PY2 else 'pycache'
|
||||
py = 'pycache'
|
||||
|
||||
if py == 'pycache':
|
||||
build.classify_renpy(pattern + "/**__pycache__/", binary)
|
||||
@@ -357,6 +357,7 @@ init python:
|
||||
|
||||
build.classify_renpy(pattern + "/**", source)
|
||||
|
||||
build.classify_renpy("renpy/gl/", None)
|
||||
|
||||
build.classify_renpy("renpy.py", "binary")
|
||||
source_and_binary("renpy")
|
||||
@@ -409,22 +410,23 @@ init python:
|
||||
build.classify_renpy("module/tinyfiledialogs/**", "source")
|
||||
build.classify_renpy("module/libhydrogen/**", "source")
|
||||
|
||||
# No-longer-needed python.
|
||||
|
||||
# all-platforms binary.
|
||||
build.classify_renpy("lib/**/*steam_api*", "steam")
|
||||
build.classify_renpy("lib/**/*Live2D*", None)
|
||||
|
||||
if PY2:
|
||||
build.classify_renpy("lib/py2-linux-armv7l/**", "linux_arm")
|
||||
build.classify_renpy("lib/py2-linux-aarch64/**", "linux_arm")
|
||||
source_and_binary("lib/py2-**", "binary", "binary")
|
||||
source_and_binary("lib/python2**", "binary", "binary")
|
||||
build.classify_renpy("renpy2.sh", "binary")
|
||||
else:
|
||||
build.classify_renpy("lib/py3-linux-armv7l/**", "linux_arm")
|
||||
build.classify_renpy("lib/py3-linux-aarch64/**", "linux_arm")
|
||||
source_and_binary("lib/py3-**", "binary", "binary")
|
||||
source_and_binary("lib/python3**", "binary", "binary", py='pyc')
|
||||
build.classify_renpy("renpy3.sh", "binary")
|
||||
build.classify_renpy("lib/py3-linux-armv7l", None)
|
||||
build.classify_renpy("lib/py3-linux-aarch64/**", "linux_arm")
|
||||
source_and_binary("lib/py3-**", "binary", "binary")
|
||||
|
||||
libpython = "lib/python{}.{}".format(sys.version_info.major, sys.version_info.minor)
|
||||
source_and_binary(libpython, "binary", "binary", py='pyc')
|
||||
|
||||
build.classify_renpy("lib/python**", None)
|
||||
build.classify_renpy("lib/py2-**", None)
|
||||
|
||||
build.classify_renpy("renpy3.sh", "binary")
|
||||
|
||||
build.classify_renpy("lib/", "binary")
|
||||
|
||||
|
||||
@@ -34,106 +34,21 @@ init python in distribute:
|
||||
from zipfile import crc32
|
||||
|
||||
# Since the long type doesn't exist on py3, define it here
|
||||
if not PY2:
|
||||
long = int
|
||||
long = int
|
||||
|
||||
zlib.Z_DEFAULT_COMPRESSION = 5
|
||||
|
||||
if PY2:
|
||||
class ZipFile(zipfile.ZipFile):
|
||||
|
||||
class ZipFile(zipfile.ZipFile):
|
||||
def write_with_info(self, zinfo, filename):
|
||||
|
||||
def write_with_info(self, zinfo, filename):
|
||||
"""Put the bytes from filename into the archive under the name
|
||||
arcname."""
|
||||
if not self.fp:
|
||||
raise RuntimeError(
|
||||
"Attempt to write to ZIP archive that was already closed")
|
||||
if zinfo.filename.endswith("/"):
|
||||
data = b''
|
||||
else:
|
||||
with open(filename, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
st = os.stat(filename)
|
||||
isdir = stat.S_ISDIR(st.st_mode)
|
||||
|
||||
zinfo.file_size = st.st_size
|
||||
zinfo.flag_bits = 0x00
|
||||
zinfo.header_offset = self.fp.tell() # Start of header bytes
|
||||
|
||||
self._writecheck(zinfo)
|
||||
self._didModify = True
|
||||
|
||||
if isdir:
|
||||
zinfo.file_size = 0
|
||||
zinfo.compress_size = 0
|
||||
zinfo.CRC = 0
|
||||
self.filelist.append(zinfo)
|
||||
self.NameToInfo[zinfo.filename] = zinfo
|
||||
self.fp.write(zinfo.FileHeader())
|
||||
return
|
||||
|
||||
with open(filename, "rb") as fp:
|
||||
# Must overwrite CRC and sizes with correct data later
|
||||
zinfo.CRC = CRC = 0
|
||||
zinfo.compress_size = compress_size = 0
|
||||
file_size = 0
|
||||
|
||||
zip64 = self._allowZip64 and \
|
||||
zinfo.file_size * 1.05 > zipfile.ZIP64_LIMIT
|
||||
|
||||
self.fp.write(zinfo.FileHeader(zip64))
|
||||
|
||||
if zinfo.compress_type == zipfile.ZIP_DEFLATED:
|
||||
cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
|
||||
zlib.DEFLATED, -15)
|
||||
else:
|
||||
cmpr = None
|
||||
while 1:
|
||||
buf = fp.read(1024 * 1024)
|
||||
if not buf:
|
||||
break
|
||||
file_size = file_size + len(buf)
|
||||
CRC = crc32(buf, CRC) & 0xffffffff
|
||||
if cmpr:
|
||||
buf = cmpr.compress(buf)
|
||||
compress_size = compress_size + len(buf)
|
||||
self.fp.write(buf)
|
||||
if cmpr:
|
||||
buf = cmpr.flush()
|
||||
compress_size = compress_size + len(buf)
|
||||
self.fp.write(buf)
|
||||
zinfo.compress_size = compress_size
|
||||
else:
|
||||
zinfo.compress_size = file_size
|
||||
zinfo.CRC = CRC
|
||||
zinfo.file_size = file_size
|
||||
|
||||
if not zip64 and self._allowZip64:
|
||||
if file_size > zipfile.ZIP64_LIMIT:
|
||||
raise RuntimeError('File size has increased during compressing')
|
||||
if compress_size > zipfile.ZIP64_LIMIT:
|
||||
raise RuntimeError('Compressed size larger than uncompressed size')
|
||||
|
||||
# Seek backwards and write CRC and file sizes
|
||||
position = self.fp.tell() # Preserve current position in file
|
||||
self.fp.seek(zinfo.header_offset, 0)
|
||||
|
||||
self.fp.write(zinfo.FileHeader(zip64))
|
||||
|
||||
self.fp.seek(position, 0)
|
||||
self.filelist.append(zinfo)
|
||||
self.NameToInfo[zinfo.filename] = zinfo
|
||||
|
||||
else:
|
||||
|
||||
class ZipFile(zipfile.ZipFile):
|
||||
|
||||
def write_with_info(self, zinfo, filename):
|
||||
|
||||
if zinfo.filename.endswith("/"):
|
||||
data = b''
|
||||
else:
|
||||
with open(filename, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
self.writestr(zinfo, data)
|
||||
self.writestr(zinfo, data)
|
||||
|
||||
|
||||
class ZipPackage(object):
|
||||
@@ -469,3 +384,5 @@ init python in distribute:
|
||||
for i in parallel_threads:
|
||||
if i.done:
|
||||
i.done()
|
||||
|
||||
parallel_threads.clear()
|
||||
|
||||
@@ -116,13 +116,32 @@ init python in project:
|
||||
|
||||
try:
|
||||
with open(os.path.join(self.path, "project.json"), "w") as f:
|
||||
json.dump(self.data, f)
|
||||
json.dump(self.data, f, indent=2)
|
||||
except Exception:
|
||||
self.load_data()
|
||||
|
||||
def update_data(self):
|
||||
data = self.data
|
||||
|
||||
data.setdefault("renpy_launcher",
|
||||
{
|
||||
"open_directory":
|
||||
{
|
||||
"game": "game",
|
||||
"base": ".",
|
||||
"images": "game/images",
|
||||
"audio": "game/audio",
|
||||
"gui": "game/gui"
|
||||
},
|
||||
"edit_file":
|
||||
{
|
||||
"script.rpy": "game/script.rpy",
|
||||
"options.rpy": "game/options.rpy",
|
||||
"gui.rpy": "game/gui.rpy",
|
||||
"screens.rpy": "game/screens.rpy"
|
||||
}
|
||||
})
|
||||
|
||||
data.setdefault("build_update", False)
|
||||
data.setdefault("packages", [ "pc", "mac" ])
|
||||
data.setdefault("add_from", True)
|
||||
@@ -352,12 +371,6 @@ init python in project:
|
||||
|
||||
line = line[:1024]
|
||||
|
||||
if PY2:
|
||||
try:
|
||||
line = line.decode("utf-8")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
m = re.search(r"#\s*TODO(\s*:\s*|\s+)(.*)", line, re.I)
|
||||
|
||||
if m is None:
|
||||
|
||||
+10
-15
@@ -440,13 +440,12 @@ init python:
|
||||
# Find the presplash and copy it over.
|
||||
presplash = None
|
||||
|
||||
if not PY2:
|
||||
for fn in [ "web-presplash.png", "web-presplash.jpg", "web-presplash.webp" ]:
|
||||
fullfn = os.path.join(project.current.path, fn)
|
||||
for fn in [ "web-presplash.png", "web-presplash.jpg", "web-presplash.webp" ]:
|
||||
fullfn = os.path.join(project.current.path, fn)
|
||||
|
||||
if os.path.exists(fullfn):
|
||||
presplash = fn
|
||||
break
|
||||
if os.path.exists(fullfn):
|
||||
presplash = fn
|
||||
break
|
||||
|
||||
if presplash:
|
||||
os.unlink(os.path.join(destination, "web-presplash.jpg"))
|
||||
@@ -456,20 +455,16 @@ init python:
|
||||
with io.open(os.path.join(WEB_PATH, "index.html"), encoding='utf-8') as f:
|
||||
html = f.read()
|
||||
|
||||
if PY2:
|
||||
html = html.replace("%%TITLE%%", display_name)
|
||||
else:
|
||||
html = html.replace("Ren'Py Web Game", display_name)
|
||||
html = html.replace("Ren'Py Web Game", display_name)
|
||||
|
||||
if presplash:
|
||||
html = html.replace("web-presplash.jpg", presplash)
|
||||
if presplash:
|
||||
html = html.replace("web-presplash.jpg", presplash)
|
||||
|
||||
with io.open(os.path.join(destination, "index.html"), "w", encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
if not PY2:
|
||||
generate_web_icons(p, destination)
|
||||
prepare_pwa_files(p, destination)
|
||||
generate_web_icons(p, destination)
|
||||
prepare_pwa_files(p, destination)
|
||||
|
||||
# Zip up the game.
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ renpy.lexersupport gen/renpy.lexersupport.c
|
||||
renpy.pydict gen/renpy.pydict.c
|
||||
renpy.style gen/renpy.style.c
|
||||
renpy.encryption gen/renpy.encryption.c
|
||||
renpy.compat.dictviews gen/renpy.compat.dictviews.c
|
||||
renpy.styledata.styleclass gen/renpy.styledata.styleclass.c
|
||||
renpy.styledata.stylesets gen/renpy.styledata.stylesets.c
|
||||
renpy.styledata.style_selected_hover_functions gen/renpy.styledata.style_selected_hover_functions.c
|
||||
@@ -25,11 +24,6 @@ renpy.display.accelerator gen/renpy.display.accelerator.c
|
||||
renpy.display.quaternion gen/renpy.display.quaternion.c
|
||||
renpy.uguu.gl gen/renpy.uguu.gl.c
|
||||
renpy.uguu.uguu gen/renpy.uguu.uguu.c
|
||||
renpy.gl.gldraw gen/renpy.gl.gldraw.c
|
||||
renpy.gl.gltexture gen/renpy.gl.gltexture.c
|
||||
renpy.gl.glenviron_shader gen/renpy.gl.glenviron_shader.c
|
||||
renpy.gl.glrtt_copy gen/renpy.gl.glrtt_copy.c
|
||||
renpy.gl.glrtt_fbo gen/renpy.gl.glrtt_fbo.c
|
||||
renpy.gl2.gl2mesh gen/renpy.gl2.gl2mesh.c
|
||||
renpy.gl2.gl2mesh2 gen/renpy.gl2.gl2mesh2.c
|
||||
renpy.gl2.gl2mesh3 gen/renpy.gl2.gl2mesh3.c
|
||||
|
||||
@@ -71,7 +71,11 @@ static int rwops_read(void *opaque, uint8_t *buf, int buf_size) {
|
||||
|
||||
}
|
||||
|
||||
#if (LIBAVFORMAT_VERSION_MAJOR < 61)
|
||||
static int rwops_write(void *opaque, uint8_t *buf, int buf_size) {
|
||||
#else
|
||||
static int rwops_write(void *opaque, const uint8_t *buf, int buf_size) {
|
||||
#endif
|
||||
printf("Writing to an SDL_rwops is a really bad idea.\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -690,9 +694,14 @@ static void decode_audio(MediaState *ms) {
|
||||
}
|
||||
|
||||
converted_frame->sample_rate = audio_sample_rate;
|
||||
#if (LIBAVUTIL_VERSION_MAJOR < 59)
|
||||
converted_frame->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||
#else
|
||||
converted_frame->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
|
||||
#endif
|
||||
converted_frame->format = AV_SAMPLE_FMT_S16;
|
||||
|
||||
#if (LIBAVUTIL_VERSION_MAJOR < 59)
|
||||
if (!ms->audio_decode_frame->channel_layout) {
|
||||
ms->audio_decode_frame->channel_layout = av_get_default_channel_layout(ms->audio_decode_frame->channels);
|
||||
|
||||
@@ -711,6 +720,26 @@ static void decode_audio(MediaState *ms) {
|
||||
swr_set_matrix(ms->swr, stereo_matrix, 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (ms->audio_decode_frame->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) {
|
||||
av_channel_layout_default(&ms->audio_decode_frame->ch_layout, ms->audio_decode_frame->ch_layout.nb_channels);
|
||||
|
||||
if (audio_equal_mono && (ms->audio_decode_frame->ch_layout.nb_channels == 1)) {
|
||||
swr_alloc_set_opts2(
|
||||
&ms->swr,
|
||||
&converted_frame->ch_layout,
|
||||
converted_frame->format,
|
||||
converted_frame->sample_rate,
|
||||
&ms->audio_decode_frame->ch_layout,
|
||||
ms->audio_decode_frame->format,
|
||||
ms->audio_decode_frame->sample_rate,
|
||||
0,
|
||||
NULL);
|
||||
|
||||
swr_set_matrix(ms->swr, stereo_matrix, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(swr_convert_frame(ms->swr, converted_frame, ms->audio_decode_frame)) {
|
||||
av_frame_free(&converted_frame);
|
||||
@@ -1159,7 +1188,11 @@ static int decode_thread(void *arg) {
|
||||
|
||||
// Compute the number of samples we need to play back.
|
||||
if (ms->audio_duration < 0) {
|
||||
#if (LIBAVFORMAT_VERSION_MAJOR < 62)
|
||||
if (av_fmt_ctx_get_duration_estimation_method(ctx) != AVFMT_DURATION_FROM_BITRATE) {
|
||||
#else
|
||||
if (ctx->duration_estimation_method != AVFMT_DURATION_FROM_BITRATE) {
|
||||
#endif
|
||||
|
||||
long long duration = ((long long) ctx->duration) * audio_sample_rate;
|
||||
ms->audio_duration = (unsigned int) (duration / AV_TIME_BASE);
|
||||
@@ -1319,7 +1352,11 @@ static int decode_sync_start(void *arg) {
|
||||
|
||||
// Compute the number of samples we need to play back.
|
||||
if (ms->audio_duration < 0) {
|
||||
#if (LIBAVFORMAT_VERSION_MAJOR < 62)
|
||||
if (av_fmt_ctx_get_duration_estimation_method(ctx) != AVFMT_DURATION_FROM_BITRATE) {
|
||||
#else
|
||||
if (ctx->duration_estimation_method != AVFMT_DURATION_FROM_BITRATE) {
|
||||
#endif
|
||||
|
||||
long long duration = ((long long) ctx->duration) * audio_sample_rate;
|
||||
ms->audio_duration = (unsigned int) (duration / AV_TIME_BASE);
|
||||
|
||||
+4
-16
@@ -154,10 +154,6 @@ cython("renpy.style")
|
||||
|
||||
cython("renpy.encryption")
|
||||
|
||||
# renpy.compat
|
||||
if PY2:
|
||||
cython("renpy.compat.dictviews")
|
||||
|
||||
# renpy.styledata
|
||||
cython("renpy.styledata.styleclass")
|
||||
cython("renpy.styledata.stylesets")
|
||||
@@ -174,12 +170,6 @@ cython("renpy.display.quaternion", libs=[ 'm' ])
|
||||
cython("renpy.uguu.gl", libs=sdl)
|
||||
cython("renpy.uguu.uguu", libs=sdl)
|
||||
|
||||
cython("renpy.gl.gldraw", libs=sdl)
|
||||
cython("renpy.gl.gltexture", libs=sdl)
|
||||
cython("renpy.gl.glenviron_shader", libs=sdl)
|
||||
cython("renpy.gl.glrtt_copy", libs=sdl)
|
||||
cython("renpy.gl.glrtt_fbo", libs=sdl)
|
||||
|
||||
cython("renpy.gl2.gl2mesh")
|
||||
cython("renpy.gl2.gl2mesh2")
|
||||
cython("renpy.gl2.gl2mesh3")
|
||||
@@ -201,12 +191,10 @@ cython(
|
||||
[ "ftsupport.c", "ttgsubtable.c" ],
|
||||
libs=sdl + [ 'freetype', 'z', 'm' ])
|
||||
|
||||
if not (PY2 and emscripten):
|
||||
|
||||
cython(
|
||||
"renpy.text.hbfont",
|
||||
[ "ftsupport.c" ],
|
||||
libs=sdl + [ 'harfbuzz', 'freetype', 'z', 'm' ])
|
||||
cython(
|
||||
"renpy.text.hbfont",
|
||||
[ "ftsupport.c" ],
|
||||
libs=sdl + [ 'harfbuzz', 'freetype', 'z', 'm' ])
|
||||
|
||||
generate_all_cython()
|
||||
find_unnecessary_gen()
|
||||
|
||||
+5
-4
@@ -28,11 +28,12 @@ import os
|
||||
import sys
|
||||
import re
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
import setuptools
|
||||
|
||||
warnings.simplefilter("ignore", category=setuptools.SetuptoolsDeprecationWarning)
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
import distutils.core as setuptools
|
||||
else:
|
||||
import setuptools
|
||||
|
||||
# This flag determines if we are compiling for Android or not.
|
||||
android = "RENPY_ANDROID" in os.environ
|
||||
|
||||
+3
-9
@@ -223,7 +223,6 @@ backup_blacklist = {
|
||||
"renpy.test.testmouse",
|
||||
"renpy.test.testparser",
|
||||
"renpy.gl2",
|
||||
"renpy.gl",
|
||||
"renpycoverage",
|
||||
}
|
||||
|
||||
@@ -254,6 +253,7 @@ name_blacklist = {
|
||||
"renpy.exports.sdl_dll",
|
||||
"renpy.sl2.slast.serial",
|
||||
"renpy.gl2.gl2draw.default_position",
|
||||
"renpy.lexer._python_updatecache",
|
||||
}
|
||||
|
||||
class Backup(_object):
|
||||
@@ -405,12 +405,9 @@ def import_all():
|
||||
# Adds in the Ren'Py loader.
|
||||
import renpy.loader
|
||||
|
||||
if not PY2:
|
||||
import renpy.py3analysis
|
||||
else:
|
||||
import renpy.py2analysis
|
||||
|
||||
import renpy.pyanalysis
|
||||
sys.modules["renpy.py3analysis"] = renpy.pyanalysis
|
||||
|
||||
import renpy.parameter
|
||||
|
||||
import renpy.ast
|
||||
@@ -479,7 +476,6 @@ def import_all():
|
||||
|
||||
sys.modules[pystr('renpy.display.text')] = renpy.text.text
|
||||
|
||||
import renpy.gl
|
||||
import renpy.gl2
|
||||
|
||||
import renpy.display.layout
|
||||
@@ -722,8 +718,6 @@ if 1 == 0:
|
||||
from . import performance
|
||||
from . import persistent
|
||||
from . import preferences
|
||||
from . import py2analysis
|
||||
from . import py3analysis
|
||||
from . import pyanalysis
|
||||
from . import pydict
|
||||
from . import python
|
||||
|
||||
+24
-4
@@ -157,10 +157,7 @@ class PyCode(object):
|
||||
if isinstance(source, PyExpr):
|
||||
loc = (source.filename, source.linenumber, source)
|
||||
|
||||
if PY2:
|
||||
self.py = 2
|
||||
else:
|
||||
self.py = 3
|
||||
self.py = 3
|
||||
|
||||
# The source code.
|
||||
self.source = source
|
||||
@@ -1065,12 +1062,35 @@ def show_imspec(imspec, atl=None):
|
||||
|
||||
if expression is not None:
|
||||
expression = renpy.python.py_eval(expression)
|
||||
|
||||
if not renpy.config.old_show_expression:
|
||||
|
||||
if isinstance(expression, str):
|
||||
name = expression
|
||||
|
||||
else:
|
||||
|
||||
if tag is None:
|
||||
|
||||
counter = 0
|
||||
|
||||
while True:
|
||||
tag = "_show_expression_%d" % counter
|
||||
if not renpy.exports.showing(tag, layer):
|
||||
break
|
||||
|
||||
counter += 1
|
||||
|
||||
name = tag
|
||||
|
||||
expression = renpy.easy.displayable(expression)
|
||||
|
||||
at_list = [ renpy.python.py_eval(i) for i in at_list ]
|
||||
|
||||
layer = renpy.exports.default_layer(layer, tag or name, bool(expression) and (tag is None))
|
||||
|
||||
|
||||
|
||||
renpy.config.show(name,
|
||||
at_list=at_list,
|
||||
layer=layer,
|
||||
|
||||
+3
-3
@@ -422,7 +422,7 @@ class ATLTransformBase(renpy.object.Object):
|
||||
rv_parameters = []
|
||||
for name, value in self.context.context.items():
|
||||
if name not in self.parameters.parameters:
|
||||
rv_parameters.append(ValuedParameter(name, ValuedParameter.KEYWORD_ONLY, value))
|
||||
rv_parameters.append(ValuedParameter(name, ValuedParameter.KEYWORD_ONLY, default=value))
|
||||
if rv_parameters:
|
||||
rv_parameters = list(self.parameters.parameters.values()) + rv_parameters
|
||||
rv_parameters.sort(key=lambda p: p.kind)
|
||||
@@ -657,10 +657,10 @@ class ATLTransformBase(renpy.object.Object):
|
||||
# continue
|
||||
|
||||
if passed: # turn into elif when possible
|
||||
param = ValuedParameter(name, param.KEYWORD_ONLY, scope[name])
|
||||
param = ValuedParameter(name, param.KEYWORD_ONLY, default=scope[name])
|
||||
|
||||
elif param.has_default:
|
||||
param = ValuedParameter(name, pkind, scope[name])
|
||||
param = ValuedParameter(name, pkind, default=scope[name])
|
||||
|
||||
else:
|
||||
## not passed and no default value
|
||||
|
||||
@@ -723,8 +723,9 @@ class Channel(object):
|
||||
with lock:
|
||||
|
||||
for filename in filenames:
|
||||
filename, _, _ = self.split_filename(filename, False)
|
||||
renpy.game.persistent._seen_audio[str(filename)] = True # type: ignore
|
||||
if renpy.exports.is_seen_allowed():
|
||||
filename, _, _ = self.split_filename(filename, False)
|
||||
renpy.game.persistent._seen_audio[str(filename)] = True # type: ignore
|
||||
|
||||
if not loop_only:
|
||||
|
||||
|
||||
+1
-10
@@ -37,12 +37,6 @@ FSENCODING = sys.getfilesystemencoding() or "utf-8"
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
|
||||
if PY2:
|
||||
sys_executable = sys.executable
|
||||
reload(sys) # type: ignore
|
||||
sys.setdefaultencoding("utf-8") # type: ignore
|
||||
sys.executable = sys_executable
|
||||
|
||||
def _setdefaultencoding(name):
|
||||
"""
|
||||
This is install in sys to prevent games from trying to change the default
|
||||
@@ -377,10 +371,7 @@ You may be using a system install of python. Please run {0}.sh,
|
||||
if hasattr(sys, "renpy_executable"):
|
||||
subprocess.Popen([sys.renpy_executable] + sys.argv[1:]) # type: ignore
|
||||
else:
|
||||
if PY2:
|
||||
subprocess.Popen([sys.executable, "-EO"] + sys.argv)
|
||||
else:
|
||||
subprocess.Popen([sys.executable] + sys.argv)
|
||||
subprocess.Popen([sys.executable] + sys.argv)
|
||||
|
||||
except renpy.game.ParseErrorException:
|
||||
pass
|
||||
|
||||
@@ -1295,9 +1295,6 @@ class ADVCharacter(object):
|
||||
|
||||
rv = renpy.substitutions.substitute(who)[0]
|
||||
|
||||
if PY2:
|
||||
rv = rv.encode("utf-8")
|
||||
|
||||
return rv
|
||||
|
||||
def __format__(self, spec):
|
||||
|
||||
@@ -111,7 +111,7 @@ init -1600 python hide:
|
||||
try:
|
||||
return self.dict[self.key]
|
||||
except LookupError as e:
|
||||
raise Exception("The {!r} {} does not exist.".format(key, self.kind)) # from e # PY3 only
|
||||
raise Exception("The {!r} {} does not exist.".format(key, self.kind)) from e
|
||||
|
||||
class ScreenVariable(Accessor):
|
||||
"""
|
||||
|
||||
@@ -544,12 +544,8 @@ init -1500 python:
|
||||
if type(self) is not type(other):
|
||||
return False
|
||||
|
||||
if PY2:
|
||||
if self.callable is not other.callable:
|
||||
return False
|
||||
else:
|
||||
if self.callable != other.callable:
|
||||
return False
|
||||
if self.callable != other.callable:
|
||||
return False
|
||||
|
||||
if self.args != other.args:
|
||||
return False
|
||||
|
||||
@@ -215,7 +215,7 @@ init -1500 python:
|
||||
try:
|
||||
self.dict[self.key] = value
|
||||
except LookupError as e:
|
||||
raise Exception("The {!r} {} does not exist".format(self.key, self.kind)) # from e # PY3 only
|
||||
raise Exception("The {!r} {} does not exist".format(self.key, self.kind)) from e
|
||||
|
||||
@renpy.pure
|
||||
class FieldValue(__GenericValue):
|
||||
|
||||
@@ -63,23 +63,13 @@ init -1500 python in build:
|
||||
|
||||
renpy_sh = "renpy.sh"
|
||||
|
||||
if PY2:
|
||||
renpy_patterns = pattern_list([
|
||||
("renpy/**.pyo", "all"),
|
||||
("renpy/**__pycache__", None),
|
||||
])
|
||||
renpy_patterns = pattern_list([
|
||||
("renpy/**__pycache__/**.{}.pyc".format(sys.implementation.cache_tag), "all"),
|
||||
("renpy/**__pycache__", "all"),
|
||||
])
|
||||
|
||||
if os.path.exists(os.path.join(config.renpy_base, "renpy2.sh")):
|
||||
renpy_sh = "renpy2.sh"
|
||||
|
||||
else:
|
||||
renpy_patterns = pattern_list([
|
||||
("renpy/**__pycache__/**.{}.pyc".format(sys.implementation.cache_tag), "all"),
|
||||
("renpy/**__pycache__", "all"),
|
||||
])
|
||||
|
||||
if os.path.exists(os.path.join(config.renpy_base, "renpy3.sh")):
|
||||
renpy_sh = "renpy3.sh"
|
||||
if os.path.exists(os.path.join(config.renpy_base, "renpy3.sh")):
|
||||
renpy_sh = "renpy3.sh"
|
||||
|
||||
|
||||
# Patterns that are used to classify Ren'Py.
|
||||
@@ -123,7 +113,7 @@ init -1500 python in build:
|
||||
( "lib/*/pythonw.exe", None),
|
||||
|
||||
# Ignore the wrong Python.
|
||||
( "lib/py3-*/" if PY2 else "lib/py2-*/", None),
|
||||
( "lib/py2-*/", None),
|
||||
|
||||
# Windows patterns.
|
||||
( "lib/py*-windows-i686/**", "windows_i686"),
|
||||
@@ -139,7 +129,7 @@ init -1500 python in build:
|
||||
( "lib/py*-mac-*/**", "mac"),
|
||||
|
||||
# Old Python library.
|
||||
( "lib/python3.*/**" if PY2 else "lib/python2.*/**", None),
|
||||
( "lib/python2.*/**", None),
|
||||
|
||||
# Shared patterns.
|
||||
( "lib/**", "windows linux mac android ios"),
|
||||
|
||||
@@ -217,7 +217,6 @@ init -1100 python:
|
||||
|
||||
if version <= (7, 4, 4):
|
||||
config.pause_after_rollback = True
|
||||
config.gl2 = False
|
||||
config.gl_lod_bias = -1.0
|
||||
config.who_what_sub_compat = 1
|
||||
|
||||
@@ -324,6 +323,9 @@ init -1100 python:
|
||||
config.box_reverse_align = True
|
||||
config.limit_transform_crop = True
|
||||
|
||||
if version <= (8, 3, 99):
|
||||
config.old_show_expression = True
|
||||
|
||||
|
||||
# The version of Ren'Py this script is intended for, or
|
||||
# None if it's intended for the current version.
|
||||
|
||||
@@ -156,12 +156,8 @@ init -1500 python in _console:
|
||||
s = s[:i] + self._ellipsis + s[len(s) - i:]
|
||||
return s
|
||||
|
||||
if PY2:
|
||||
repr_str = _repr_bytes
|
||||
repr_unicode = _repr_string
|
||||
else:
|
||||
repr_bytes = _repr_bytes
|
||||
repr_str = _repr_string
|
||||
repr_bytes = _repr_bytes
|
||||
repr_str = _repr_string
|
||||
|
||||
def repr_tuple(self, x, level):
|
||||
if not x: return "()"
|
||||
@@ -212,7 +208,7 @@ init -1500 python in _console:
|
||||
|
||||
if level <= 0: return "{...}"
|
||||
|
||||
iter_keys = self._to_shorted_list(x, self.maxdict, sort=PY2)
|
||||
iter_keys = self._to_shorted_list(x, self.maxdict, sort=False)
|
||||
iter_x = self._make_pretty_items(x, iter_keys, '{', '}')
|
||||
return self._repr_iterable(iter_x, level, '{', '}')
|
||||
|
||||
@@ -227,7 +223,7 @@ init -1500 python in _console:
|
||||
|
||||
if level <= 0: return left + "...})"
|
||||
|
||||
iter_keys = self._to_shorted_list(x, self.maxdict, sort=PY2)
|
||||
iter_keys = self._to_shorted_list(x, self.maxdict, sort=False)
|
||||
iter_x = self._make_pretty_items(x, iter_keys, left, '})')
|
||||
return self._repr_iterable(iter_x, level, left, '})')
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ init -1500:
|
||||
python:
|
||||
class _SetRenderer(Action):
|
||||
"""
|
||||
Sets the preferred renderer to one of "auto", "angle", "gl", or
|
||||
"sw".
|
||||
Sets the preferred renderer.
|
||||
"""
|
||||
|
||||
def __init__(self, renderer):
|
||||
@@ -95,24 +94,6 @@ init -1500:
|
||||
action _SetRenderer("auto")
|
||||
style_suffix "radio_button"
|
||||
|
||||
if not config.gl2:
|
||||
|
||||
if gl:
|
||||
textbutton _("Force GL Renderer"):
|
||||
action _SetRenderer("gl")
|
||||
style_suffix "radio_button"
|
||||
|
||||
if angle:
|
||||
textbutton _("Force ANGLE Renderer"):
|
||||
action _SetRenderer("angle")
|
||||
style_suffix "radio_button"
|
||||
|
||||
if gles:
|
||||
textbutton _("Force GLES Renderer"):
|
||||
action _SetRenderer("gles")
|
||||
style_suffix "radio_button"
|
||||
|
||||
|
||||
if gl:
|
||||
textbutton _("Force GL2 Renderer"):
|
||||
action _SetRenderer("gl2")
|
||||
@@ -349,16 +330,11 @@ init -1500 python:
|
||||
|
||||
renderer_info = renpy.get_renderer_info()
|
||||
|
||||
# Software renderer check.
|
||||
# Software renderer check.
|
||||
if config.renderer != "sw" and renderer_info["renderer"] == "sw":
|
||||
problem = "sw"
|
||||
allow_continue = False
|
||||
|
||||
# Game require gl2 that wasn't initialized.
|
||||
elif config.gl2 and not renderer_info.get("models", False):
|
||||
problem = "gl2"
|
||||
allow_continue = False
|
||||
|
||||
if problem is None:
|
||||
return
|
||||
|
||||
|
||||
@@ -273,8 +273,8 @@ init -1510 python:
|
||||
def get_text(self):
|
||||
try:
|
||||
return super(LocalVariableInputValue, self).get_text()
|
||||
except LookupError:
|
||||
raise Exception("The {!r} local variable does not exist.".format(self.key)) # from e # PY3 only
|
||||
except LookupError as e:
|
||||
raise Exception("The {!r} local variable does not exist.".format(self.key)) from e
|
||||
|
||||
init -1510 python hide:
|
||||
if config.generating_documentation:
|
||||
|
||||
@@ -173,7 +173,7 @@ init -1800:
|
||||
adjust_spacing True
|
||||
emoji_font "TwemojiCOLRv0.ttf"
|
||||
prefer_emoji True
|
||||
shaper ("harfbuzz" if (not PY2) else "freetype")
|
||||
shaper "harfbuzz"
|
||||
|
||||
# Window properties
|
||||
background None
|
||||
|
||||
+3
-18
@@ -79,9 +79,6 @@ init 1100 python:
|
||||
else:
|
||||
config.has_sync = None
|
||||
|
||||
if renpy.emscripten and PY2:
|
||||
config.has_sync = None
|
||||
|
||||
init -1100 python in _sync:
|
||||
|
||||
# Do not participate in saves.
|
||||
@@ -149,10 +146,7 @@ init -1100 python in _sync:
|
||||
for _ in range(10000):
|
||||
hashed = hashlib.sha256(hashed).digest()
|
||||
|
||||
if PY2:
|
||||
return hashed.encode("hex")
|
||||
else:
|
||||
return hashed.hex()
|
||||
return hashed.hex()
|
||||
|
||||
|
||||
def key_and_hash(sync_id):
|
||||
@@ -175,10 +169,7 @@ init -1100 python in _sync:
|
||||
for _ in range(10000):
|
||||
hashed = hashlib.sha256(hashed).digest()
|
||||
|
||||
if PY2:
|
||||
return key, hashed.encode("hex")
|
||||
else:
|
||||
return key, hashed.hex()
|
||||
return key, hashed.hex()
|
||||
|
||||
def verbose_error(e):
|
||||
renpy.display.log.write("Sync error:")
|
||||
@@ -260,8 +251,6 @@ init -1100 python in _sync:
|
||||
|
||||
sd = renpy.config.save_directory
|
||||
if sd:
|
||||
if PY2:
|
||||
sd = sd.encode("utf-8")
|
||||
zf.writestr("save_directory", sd)
|
||||
|
||||
persistent = location.path("persistent")[1]
|
||||
@@ -388,11 +377,7 @@ init -1100 python in _sync:
|
||||
|
||||
zi = zf.getinfo(fn)
|
||||
|
||||
if PY2:
|
||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||
timestamp = (datetime.datetime(*zi.date_time) - epoch).total_seconds()
|
||||
else:
|
||||
timestamp = datetime.datetime(*zi.date_time).timestamp()
|
||||
timestamp = datetime.datetime(*zi.date_time).timestamp()
|
||||
|
||||
data = zf.read(fn)
|
||||
|
||||
|
||||
@@ -117,8 +117,20 @@ screen _image_attributes():
|
||||
|
||||
for l in renpy.display.scenelists.ordered_layers:
|
||||
|
||||
$ hidden = renpy.get_hidden_tags(l)
|
||||
$ showing = renpy.get_showing_tags(l, sort=True)
|
||||
python:
|
||||
|
||||
hidden = renpy.get_hidden_tags(l)
|
||||
showing = renpy.get_showing_tags(l, sort=True)
|
||||
transforms = renpy.layer_has_transforms(l)
|
||||
transform_list = [ ]
|
||||
|
||||
if transforms.at_list:
|
||||
transform_list.append("show layer")
|
||||
if transforms.camera:
|
||||
transform_list.append("camera")
|
||||
if transforms.config_layer_transforms:
|
||||
transform_list.append("config.layer_transforms")
|
||||
|
||||
|
||||
if not hidden and not showing:
|
||||
continue
|
||||
@@ -128,6 +140,9 @@ screen _image_attributes():
|
||||
|
||||
text _("Layer [l]:")
|
||||
|
||||
if transform_list:
|
||||
text _(" (transforms: [', '.join(transform_list)])")
|
||||
|
||||
for name in hidden:
|
||||
$ attributes = " ".join(renpy.get_attributes(name, layer=l))
|
||||
text _(" [name!q] [attributes!q] (hidden)")
|
||||
@@ -499,10 +514,7 @@ label _filename_list:
|
||||
|
||||
for fn in files:
|
||||
fn = os.path.join(dirname, fn)
|
||||
if PY2:
|
||||
fn = fn.encode("utf-8", "replace")
|
||||
print(fn, file=f)
|
||||
print(fn)
|
||||
|
||||
renpy.launch_editor([ FILES_TXT ], transient=1)
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ init label _errorhandling:
|
||||
adjust_spacing True
|
||||
emoji_font "TwemojiCOLRv0.ttf"
|
||||
prefer_emoji True
|
||||
shaper ("harfbuzz" if (not PY2) else "freetype")
|
||||
shaper "harfbuzz"
|
||||
|
||||
# Window properties
|
||||
background None
|
||||
@@ -509,50 +509,69 @@ init python:
|
||||
|
||||
return "\n".join(rv)
|
||||
|
||||
def __format_parse_errors(s):
|
||||
def __format_parse_errors(errors: list[str]):
|
||||
"""
|
||||
Takes list of ParseError messages and transforms it
|
||||
to string of sane length to be used as child of viewport.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
rv = ""
|
||||
rv = []
|
||||
rv_len = 0
|
||||
for error in errors:
|
||||
lines = error.split("\n")
|
||||
|
||||
lines = s.split("\n")
|
||||
lines = __limit_lines(lines)
|
||||
len_lines = len(lines)
|
||||
# Do not count first line in total to include those as much as
|
||||
# possible.
|
||||
rv.append(re.sub(
|
||||
r'(File "(.*)", line (\d+))',
|
||||
r'{a=edit:\3:\2}\1{/a}',
|
||||
lines.pop(0)))
|
||||
|
||||
ln = 0
|
||||
error_rv = []
|
||||
error_len = 0
|
||||
for line in lines:
|
||||
# In case we have single insanely lengthy single error.
|
||||
error_len += len(line)
|
||||
if error_len > 65536:
|
||||
error_rv.clear()
|
||||
break
|
||||
|
||||
while ln < len_lines:
|
||||
line = lines[ln]
|
||||
ln += 1
|
||||
if set(line.strip()) == {"^"}:
|
||||
idx = line.find("^")
|
||||
count = line.count("^", idx)
|
||||
|
||||
if ln < len_lines and lines[ln].endswith("^"):
|
||||
highlight = len(lines[ln]) - 1
|
||||
ln += 1
|
||||
else:
|
||||
highlight = -1
|
||||
prev_line = error_rv[-1]
|
||||
|
||||
pos = 0
|
||||
line = prev_line[:idx]
|
||||
|
||||
for c in line:
|
||||
if pos == highlight:
|
||||
rv += u"{color=#c00}\u2192{/color}"
|
||||
highlight = -1
|
||||
if count == 1:
|
||||
if len(prev_line) < idx + count:
|
||||
mark = "\u2190"
|
||||
else:
|
||||
mark = "\u2192"
|
||||
count = 0
|
||||
else:
|
||||
mark = prev_line[idx:idx + count]
|
||||
line += "{b}{color=#a00}"
|
||||
line += mark
|
||||
line += "{/color}{/b}"
|
||||
line += prev_line[idx + count:]
|
||||
|
||||
pos += 1
|
||||
error_rv[-1] = line
|
||||
continue
|
||||
|
||||
if c == "{":
|
||||
rv += "{{"
|
||||
else:
|
||||
rv += c
|
||||
error_rv.append(line)
|
||||
|
||||
if highlight > 0:
|
||||
rv += u"{color=#c00}\u2190{/color}"
|
||||
rv_len += error_len
|
||||
if rv_len > 1048576:
|
||||
break
|
||||
|
||||
rv += "\n"
|
||||
rv.extend(error_rv)
|
||||
rv.append("")
|
||||
|
||||
|
||||
rv = re.sub(r'(File "(.*)", line (\d+))', r'{a=edit:\3:\2}\1{/a}', rv)
|
||||
|
||||
return rv
|
||||
return "\n".join(rv)
|
||||
|
||||
class _EditFile(Action):
|
||||
def __init__(self, filename, line=1):
|
||||
@@ -778,7 +797,7 @@ screen _exception:
|
||||
key "console" action __EnterConsole()
|
||||
|
||||
# The screen that is used for error handling.
|
||||
screen _parse_errors:
|
||||
screen _parse_errors(errors, error_fn, reload_action):
|
||||
modal True
|
||||
layer config.interface_layer
|
||||
zorder 1090
|
||||
|
||||
+20
-96
@@ -58,8 +58,6 @@ Right now, it does the following things:
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
|
||||
import future.standard_library
|
||||
import future.utils
|
||||
import builtins
|
||||
|
||||
import io
|
||||
@@ -68,25 +66,15 @@ import operator
|
||||
|
||||
python_open = open
|
||||
|
||||
################################################################################
|
||||
# Alias the Python 3 standard library.
|
||||
|
||||
future.standard_library.install_aliases()
|
||||
|
||||
################################################################################
|
||||
# Determine if this is Python2.
|
||||
|
||||
PY2 = future.utils.PY2
|
||||
PY2 = False
|
||||
|
||||
################################################################################
|
||||
# Make open mimic Python 3.
|
||||
|
||||
if PY2:
|
||||
open = io.open
|
||||
import re
|
||||
re.Pattern = re._pattern_type # type:ignore
|
||||
else:
|
||||
open = builtins.open
|
||||
open = builtins.open
|
||||
|
||||
|
||||
def compat_open(*args, **kwargs):
|
||||
@@ -97,121 +85,57 @@ def compat_open(*args, **kwargs):
|
||||
|
||||
|
||||
################################################################################
|
||||
# Make strict use surrogateescape error handling on PY2.
|
||||
# Codecs.
|
||||
|
||||
import codecs
|
||||
|
||||
strict_error = codecs.lookup_error("strict")
|
||||
codecs.register_error("python_strict", strict_error)
|
||||
|
||||
if PY2:
|
||||
surrogateescape_error = codecs.lookup_error("surrogateescape")
|
||||
codecs.register_error("strict", surrogateescape_error)
|
||||
|
||||
import renpy
|
||||
renpy.update_path()
|
||||
|
||||
################################################################################
|
||||
# String (text and binary) types and functions.
|
||||
|
||||
basestring = future.utils.string_types
|
||||
basestring = (str, bytes)
|
||||
pystr = str
|
||||
unicode = future.utils.text_type
|
||||
unicode = str
|
||||
str = builtins.str
|
||||
|
||||
# This tries to help pylance get the types right.
|
||||
str = builtins.str; globals()["str"] = future.utils.text_type
|
||||
def bord(s): # type: (bytes) -> int
|
||||
return s[0]
|
||||
|
||||
def bchr(i): # type: (int) -> bytes
|
||||
return bytes([i])
|
||||
|
||||
bord = future.utils.bord
|
||||
|
||||
if PY2:
|
||||
bchr = chr # type: ignore
|
||||
else:
|
||||
def bchr(i): # type: (int) -> bytes
|
||||
return bytes([i])
|
||||
tobytes = future.utils.tobytes
|
||||
|
||||
from future.builtins import chr
|
||||
|
||||
################################################################################
|
||||
# Dictionary views.
|
||||
|
||||
# The try block solves a chicken-and-egg problem when dictviews is not
|
||||
# compiled yet, as part of the Ren'Py build process.
|
||||
|
||||
def add_attribute(obj, name, value):
|
||||
pass
|
||||
|
||||
if PY2:
|
||||
try:
|
||||
from renpy.compat.dictviews import add_attribute
|
||||
except ImportError:
|
||||
print("Could not import renpy.compat.dictviews.", file=sys.stderr)
|
||||
def tobytes(s):
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
else:
|
||||
if isinstance(s, str):
|
||||
return s.encode('latin-1')
|
||||
else:
|
||||
return bytes(s)
|
||||
|
||||
chr = builtins.chr
|
||||
|
||||
################################################################################
|
||||
# Range.
|
||||
|
||||
if PY2:
|
||||
range = xrange # type: ignore
|
||||
else:
|
||||
range = builtins.range
|
||||
range = builtins.range
|
||||
|
||||
################################################################################
|
||||
# Round.
|
||||
|
||||
round = builtins.round
|
||||
|
||||
################################################################################
|
||||
# Allow TextIOWrapper to take utf8-bytes.
|
||||
|
||||
if PY2:
|
||||
|
||||
import types
|
||||
|
||||
# io.TextIOWrapper._write = io.TextIOWrapper.write
|
||||
|
||||
def text_write(self, s):
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode("utf-8", "surrogateescape")
|
||||
return self._write(s)
|
||||
|
||||
add_attribute(io.TextIOWrapper, "_write", io.TextIOWrapper.write)
|
||||
add_attribute(io.TextIOWrapper, "write", types.MethodType(text_write, None, io.TextIOWrapper)) # type: ignore
|
||||
|
||||
################################################################################
|
||||
# Chance the default for subprocess.Popen.
|
||||
if PY2:
|
||||
import subprocess
|
||||
if hasattr(subprocess, 'Popen'): # Web2 does not have subprocess.Popen
|
||||
class Popen(subprocess.Popen):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if ("stdout" not in kwargs) and ("stderr" not in kwargs) and ("stdin" not in kwargs):
|
||||
kwargs.setdefault("close_fds", True)
|
||||
super(Popen, self).__init__(*args, **kwargs)
|
||||
|
||||
subprocess.Popen = Popen
|
||||
|
||||
################################################################################
|
||||
# Intern
|
||||
|
||||
if PY2:
|
||||
intern_cache = {}
|
||||
|
||||
def intern(s):
|
||||
return intern_cache.setdefault(s, s)
|
||||
|
||||
sys.intern = intern
|
||||
|
||||
################################################################################
|
||||
# Export functions.
|
||||
|
||||
__all__ = [ "PY2", "open", "basestring", "str", "pystr", "range",
|
||||
"round", "bord", "bchr", "tobytes", "chr", "unicode", ]
|
||||
|
||||
if PY2:
|
||||
__all__ = [ bytes(i) for i in __all__ ] # type: ignore
|
||||
|
||||
|
||||
# Generated by scripts/relative_imports.py, do not edit below this line.
|
||||
if 1 == 0:
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from cpython.object cimport PyObject
|
||||
import types
|
||||
|
||||
# Import the Python internals. Here be dragons, but at least Python 2.7 isn't
|
||||
# going to be changing.
|
||||
|
||||
cdef extern from "Python.h":
|
||||
ctypedef struct PyTypeObject
|
||||
cdef void PyType_Modified(PyTypeObject *)
|
||||
|
||||
ctypedef struct PyCodeObject:
|
||||
int co_flags
|
||||
PyObject *co_filename
|
||||
|
||||
cdef extern from "frameobject.h":
|
||||
|
||||
ctypedef struct PyFrameObject:
|
||||
PyFrameObject *f_back
|
||||
PyCodeObject *f_code
|
||||
|
||||
cdef extern from "Python.h":
|
||||
|
||||
ctypedef struct PyThreadState:
|
||||
PyFrameObject *frame
|
||||
|
||||
PyThreadState *PyThreadState_Get()
|
||||
|
||||
enum:
|
||||
CO_FUTURE_DIVISION
|
||||
CO_FUTURE_WITH_STATEMENT
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Add attributes to built in types.
|
||||
|
||||
cdef class MappingProxy:
|
||||
"""
|
||||
This is something that Python's built-in mapping proxy can be converted
|
||||
to, in order to gain access to the internal dictionary so we can make
|
||||
changes to it.
|
||||
"""
|
||||
|
||||
cdef dict dict
|
||||
|
||||
|
||||
def add_attribute(cls, name, value):
|
||||
"""
|
||||
This adds an attribute to a Python built-in type.
|
||||
"""
|
||||
|
||||
cdef MappingProxy mp = <MappingProxy> <PyObject *> (cls.__dict__)
|
||||
mp.dict[name] = value
|
||||
|
||||
PyType_Modified(<PyTypeObject *> cls)
|
||||
|
||||
|
||||
################################################################################
|
||||
# dict
|
||||
#
|
||||
# For the .items(), .keys(), and .values() methods, check to see if the calling
|
||||
# code was compiled with "from __future__ import division", or the equivalent.
|
||||
# If it was, then invoke Python 3 semantics for these methods, which means
|
||||
# returning a view into a dict. Otherwise, go with Python 2.
|
||||
#
|
||||
# Why division? It's the most Python 3-specific of the flags, at least for
|
||||
# Ren'Py purposes.
|
||||
|
||||
add_attribute(dict, "_items", dict.items)
|
||||
add_attribute(dict, "_keys", dict.keys)
|
||||
add_attribute(dict, "_values", dict.values)
|
||||
|
||||
cdef bint use_view():
|
||||
"""
|
||||
Returns true if the methods should use view semantics, or false if
|
||||
they should use legacy/list semantics.
|
||||
"""
|
||||
|
||||
return ((PyThreadState_Get().frame.f_code.co_flags) & (CO_FUTURE_DIVISION | CO_FUTURE_WITH_STATEMENT)) == (CO_FUTURE_DIVISION | CO_FUTURE_WITH_STATEMENT)
|
||||
|
||||
|
||||
def items(self):
|
||||
if use_view():
|
||||
return self.viewitems()
|
||||
else:
|
||||
return self._items()
|
||||
|
||||
def keys(self):
|
||||
if use_view():
|
||||
return self.viewkeys()
|
||||
else:
|
||||
return self._keys()
|
||||
|
||||
def values(self):
|
||||
if use_view():
|
||||
return self.viewvalues()
|
||||
else:
|
||||
return self._values()
|
||||
|
||||
|
||||
add_attribute(dict, "items", types.MethodType(items, None, dict))
|
||||
add_attribute(dict, "keys", types.MethodType(keys, None, dict))
|
||||
add_attribute(dict, "values", types.MethodType(values, None, dict))
|
||||
|
||||
+191
-221
@@ -27,260 +27,230 @@ import renpy
|
||||
import pickle
|
||||
import io
|
||||
|
||||
# Protocol 2 can be loaded on Python 2 and Python 3.
|
||||
PROTOCOL = 2
|
||||
PROTOCOL = pickle.HIGHEST_PROTOCOL
|
||||
|
||||
if PY2:
|
||||
|
||||
import cPickle # type: ignore
|
||||
import functools
|
||||
import datetime
|
||||
import ast
|
||||
|
||||
def make_datetime(cls, *args, **kwargs):
|
||||
"""
|
||||
Makes a datetime.date, datetime.time, or datetime.datetime object
|
||||
from a surrogateescaped str. This is used when unpickling a datetime
|
||||
object that was first created in Python 2.
|
||||
"""
|
||||
|
||||
if (len(args) == 1) and isinstance(args[0], str):
|
||||
data = args[0].encode("utf-8", "surrogateescape")
|
||||
return cls.__new__(cls, data.decode("latin-1"))
|
||||
|
||||
return cls.__new__(cls, *args, **kwargs)
|
||||
|
||||
class Unpickler(pickle.Unpickler):
|
||||
date = functools.partial(make_datetime, datetime.date)
|
||||
time = functools.partial(make_datetime, datetime.time)
|
||||
datetime = functools.partial(make_datetime, datetime.datetime)
|
||||
|
||||
def find_class(self, module, name):
|
||||
if module == "datetime":
|
||||
if name == "date":
|
||||
return self.date
|
||||
elif name == "time":
|
||||
return self.time
|
||||
elif name == "datetime":
|
||||
return self.datetime
|
||||
|
||||
if module == "_ast" and name in REWRITE_NODES:
|
||||
return REWRITE_NODES[name]
|
||||
|
||||
return super().find_class(module, name)
|
||||
|
||||
def load(f):
|
||||
up = Unpickler(f, fix_imports=True, encoding="utf-8", errors="surrogateescape")
|
||||
return up.load()
|
||||
|
||||
def loads(s):
|
||||
return load(io.BytesIO(s))
|
||||
|
||||
def dump(o, f, highest=False):
|
||||
pickle.dump(o, f, pickle.HIGHEST_PROTOCOL if highest else PROTOCOL)
|
||||
|
||||
def dumps(o, highest=False):
|
||||
return pickle.dumps(o, pickle.HIGHEST_PROTOCOL if highest else PROTOCOL)
|
||||
|
||||
# The python AST module changed significantly between python 2 and 3. Old-style
|
||||
# screenlang support records raw python ast nodes into the rpyc data, making these
|
||||
# impossible to load normally. This dict contains mappings of nodes that need to be
|
||||
# modified to wrapper classes with a custom __setstate__ implementation that will
|
||||
# cause the pickle machinery to do the right thing.
|
||||
# There are some things that cannot be handled by this
|
||||
# (as the type of the node to be emitted is not fixed at that point), so those
|
||||
# are handled by a NodeTransformer in the ast.Module replacement.
|
||||
# Note: this isn't a complete python 2 -> python 3 ast conversion, we just convert
|
||||
# what is needed to still support old-style screens (Ren'py 6.17 and below)
|
||||
# mapping of "classname": WrapperClass
|
||||
REWRITE_NODES = {}
|
||||
|
||||
# NodeTransformer that runs after the ast has been instantiated in the ast.Module
|
||||
# handler, and allows us to fix some more difficult issues. Currently only
|
||||
# handles converting ast.Name nodes that contain True/False/None into the appropriate
|
||||
# ast.Constant nodes.
|
||||
class AstFixupTransformer(ast.NodeTransformer):
|
||||
def visit_Name(self, node):
|
||||
# in python 2 True, False, None are keywords, and get parsed as such.
|
||||
if node.id == "True":
|
||||
alt_node = ast.Constant(True)
|
||||
|
||||
elif node.id == "False":
|
||||
alt_node = ast.Constant(False)
|
||||
|
||||
elif node.id == "None":
|
||||
alt_node = ast.Constant(None)
|
||||
|
||||
def load(f): # type: ignore
|
||||
if renpy.config.use_cpickle:
|
||||
return cPickle.load(f)
|
||||
else:
|
||||
return pickle.load(f)
|
||||
return node
|
||||
|
||||
def loads(s): # type: ignore
|
||||
if renpy.config.use_cpickle:
|
||||
return cPickle.loads(s)
|
||||
else:
|
||||
return pickle.loads(s)
|
||||
alt_node.lineno = node.lineno
|
||||
alt_node.col_offset = node.col_offset
|
||||
return alt_node
|
||||
|
||||
def dump(o, f, highest=False):
|
||||
if renpy.config.use_cpickle:
|
||||
cPickle.dump(o, f, PROTOCOL)
|
||||
else:
|
||||
pickle.dump(o, f,PROTOCOL)
|
||||
# wrapper classes. They all have __setstate__ defined to handle converting from the
|
||||
# py2 class, and __reduce__ implemented to convert to the underlying py3 class
|
||||
# if it gets repickled. Note that py3 ast classes will not hit the REWRITE_NODES check
|
||||
# as py3 classes report to be from "ast" instead of "_ast"
|
||||
class CallWrapper(ast.Call):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Call, args, attrs
|
||||
|
||||
def dumps(o, highest=False): # type: ignore
|
||||
if renpy.config.use_cpickle:
|
||||
return cPickle.dumps(o, PROTOCOL)
|
||||
else:
|
||||
return pickle.dumps(o, PROTOCOL)
|
||||
def __setstate__(self, state):
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
|
||||
else:
|
||||
# contents
|
||||
self.func = state["func"]
|
||||
self.args = state["args"]
|
||||
self.keywords = state["keywords"]
|
||||
|
||||
import functools
|
||||
import datetime
|
||||
import ast
|
||||
# these gained some extra info
|
||||
for keyword in self.keywords:
|
||||
keyword.lineno = self.lineno
|
||||
keyword.col_offset = self.col_offset
|
||||
|
||||
def make_datetime(cls, *args, **kwargs):
|
||||
"""
|
||||
Makes a datetime.date, datetime.time, or datetime.datetime object
|
||||
from a surrogateescaped str. This is used when unpickling a datetime
|
||||
object that was first created in Python 2.
|
||||
"""
|
||||
# these are no longer an extra field, they're just part of args and keywords
|
||||
# as you can now supply multiple of them.
|
||||
if state["starargs"]:
|
||||
node = ast.Starred(value=state["starargs"], ctx=ast.Load())
|
||||
node.lineno = self.lineno
|
||||
node.col_offset = self.col_offset
|
||||
self.args.append(node)
|
||||
|
||||
if (len(args) == 1) and isinstance(args[0], str):
|
||||
data = args[0].encode("utf-8", "surrogateescape")
|
||||
return cls.__new__(cls, data.decode("latin-1"))
|
||||
if state["kwargs"]:
|
||||
node = ast.keyword(None, state["kwargs"])
|
||||
node.lineno = self.lineno
|
||||
node.col_offset = self.col_offset
|
||||
self.keywords.append(node)
|
||||
|
||||
return cls.__new__(cls, *args, **kwargs)
|
||||
REWRITE_NODES["Call"] = CallWrapper
|
||||
|
||||
class Unpickler(pickle.Unpickler):
|
||||
date = functools.partial(make_datetime, datetime.date)
|
||||
time = functools.partial(make_datetime, datetime.time)
|
||||
datetime = functools.partial(make_datetime, datetime.datetime)
|
||||
class NumWrapper(ast.Constant):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Constant, args, attrs
|
||||
|
||||
def find_class(self, module, name):
|
||||
if module == "datetime":
|
||||
if name == "date":
|
||||
return self.date
|
||||
elif name == "time":
|
||||
return self.time
|
||||
elif name == "datetime":
|
||||
return self.datetime
|
||||
def __setstate__(self, state):
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
|
||||
if module == "_ast" and name in REWRITE_NODES:
|
||||
return REWRITE_NODES[name]
|
||||
# contents
|
||||
self.value = state["n"]
|
||||
|
||||
return super().find_class(module, name)
|
||||
REWRITE_NODES["Num"] = NumWrapper
|
||||
|
||||
def load(f):
|
||||
up = Unpickler(f, fix_imports=True, encoding="utf-8", errors="surrogateescape")
|
||||
return up.load()
|
||||
class StrWrapper(ast.Constant):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Constant, args, attrs
|
||||
|
||||
def loads(s):
|
||||
return load(io.BytesIO(s))
|
||||
def __setstate__(self, state):
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
|
||||
def dump(o, f, highest=False):
|
||||
pickle.dump(o, f, pickle.HIGHEST_PROTOCOL if highest else PROTOCOL)
|
||||
# contents
|
||||
self.value = state["s"]
|
||||
|
||||
def dumps(o, highest=False):
|
||||
return pickle.dumps(o, pickle.HIGHEST_PROTOCOL if highest else PROTOCOL)
|
||||
REWRITE_NODES["Str"] = StrWrapper
|
||||
|
||||
# The python AST module changed significantly between python 2 and 3. Old-style
|
||||
# screenlang support records raw python ast nodes into the rpyc data, making these
|
||||
# impossible to load normally. This dict contains mappings of nodes that need to be
|
||||
# modified to wrapper classes with a custom __setstate__ implementation that will
|
||||
# cause the pickle machinery to do the right thing.
|
||||
# There are some things that cannot be handled by this
|
||||
# (as the type of the node to be emitted is not fixed at that point), so those
|
||||
# are handled by a NodeTransformer in the ast.Module replacement.
|
||||
# Note: this isn't a complete python 2 -> python 3 ast conversion, we just convert
|
||||
# what is needed to still support old-style screens (Ren'py 6.17 and below)
|
||||
# mapping of "classname": WrapperClass
|
||||
REWRITE_NODES = {}
|
||||
class ModuleWrapper(ast.Module):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Module, args, attrs
|
||||
|
||||
# NodeTransformer that runs after the ast has been instantiated in the ast.Module
|
||||
# handler, and allows us to fix some more difficult issues. Currently only
|
||||
# handles converting ast.Name nodes that contain True/False/None into the appropriate
|
||||
# ast.Constant nodes.
|
||||
class AstFixupTransformer(ast.NodeTransformer):
|
||||
def visit_Name(self, node):
|
||||
# in python 2 True, False, None are keywords, and get parsed as such.
|
||||
if node.id == "True":
|
||||
alt_node = ast.Constant(True)
|
||||
def __setstate__(self, state):
|
||||
# contents
|
||||
self.body = state["body"]
|
||||
self.type_ignores = []
|
||||
|
||||
elif node.id == "False":
|
||||
alt_node = ast.Constant(False)
|
||||
# this is the root node, so now is a good moment do do some transforms we couldn't
|
||||
# do earlier because we weren't sure of the node type to be created.
|
||||
|
||||
elif node.id == "None":
|
||||
alt_node = ast.Constant(None)
|
||||
transformer = AstFixupTransformer()
|
||||
transformer.visit(self)
|
||||
|
||||
else:
|
||||
return node
|
||||
REWRITE_NODES["Module"] = ModuleWrapper
|
||||
|
||||
alt_node.lineno = node.lineno
|
||||
alt_node.col_offset = node.col_offset
|
||||
return alt_node
|
||||
class ReprWrapper(ast.Call):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Call, args, attrs
|
||||
|
||||
# wrapper classes. They all have __setstate__ defined to handle converting from the
|
||||
# py2 class, and __reduce__ implemented to convert to the underlying py3 class
|
||||
# if it gets repickled. Note that py3 ast classes will not hit the REWRITE_NODES check
|
||||
# as py3 classes report to be from "ast" instead of "_ast"
|
||||
class CallWrapper(ast.Call):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Call, args, attrs
|
||||
def __setstate__(self, state):
|
||||
# we need to transform `thing` into repr(thing)
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
|
||||
def __setstate__(self, state):
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
# contents
|
||||
self.func = ast.Name("repr", ast.Load(), lineno=self.lineno, col_offset=self.col_offset)
|
||||
self.args = [state["value"]]
|
||||
self.keywords = []
|
||||
|
||||
# contents
|
||||
self.func = state["func"]
|
||||
self.args = state["args"]
|
||||
self.keywords = state["keywords"]
|
||||
REWRITE_NODES["Repr"] = ReprWrapper
|
||||
|
||||
# these gained some extra info
|
||||
for keyword in self.keywords:
|
||||
keyword.lineno = self.lineno
|
||||
keyword.col_offset = self.col_offset
|
||||
class ArgumentsWrapper(ast.arguments):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.arguments, args, attrs
|
||||
|
||||
# these are no longer an extra field, they're just part of args and keywords
|
||||
# as you can now supply multiple of them.
|
||||
if state["starargs"]:
|
||||
node = ast.Starred(value=state["starargs"], ctx=ast.Load())
|
||||
node.lineno = self.lineno
|
||||
node.col_offset = self.col_offset
|
||||
self.args.append(node)
|
||||
def __setstate__(self, state):
|
||||
# source info: this node doesn't get source info
|
||||
|
||||
if state["kwargs"]:
|
||||
node = ast.keyword(None, state["kwargs"])
|
||||
node.lineno = self.lineno
|
||||
node.col_offset = self.col_offset
|
||||
self.keywords.append(node)
|
||||
def make_arg(name):
|
||||
# python 2 just uses bare ast.Name nodes as arguments
|
||||
# technically it also can support more complex tuple destructuring
|
||||
# expressions in here, but python 3 just doesn't support that,
|
||||
# and there's really no good way of exactly handling that crazyness.
|
||||
assert isinstance(name, ast.Name)
|
||||
return ast.arg(name.id, lineno=name.lineno, col_offset=name.col_offset)
|
||||
|
||||
REWRITE_NODES["Call"] = CallWrapper
|
||||
# contents. source doesn't record lineno/col_offset for vararg/kwarg
|
||||
self.posonlyargs = []
|
||||
self.args = [make_arg(i) for i in state["args"]]
|
||||
self.vararg = ast.arg(state["vararg"], lineno=1, col_offset=0)
|
||||
self.kwonlyargs = []
|
||||
self.kw_defaults = []
|
||||
self.kwarg = ast.arg(state["kwarg"], lineno=1, col_offset=0)
|
||||
self.defaults = state["defaults"]
|
||||
|
||||
class NumWrapper(ast.Constant):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Constant, args, attrs
|
||||
REWRITE_NODES["arguments"] = ArgumentsWrapper
|
||||
|
||||
def __setstate__(self, state):
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
class ParamWrapper(ast.Load):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Load, args, attrs
|
||||
|
||||
# contents
|
||||
self.value = state["n"]
|
||||
|
||||
REWRITE_NODES["Num"] = NumWrapper
|
||||
|
||||
class StrWrapper(ast.Constant):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Constant, args, attrs
|
||||
|
||||
def __setstate__(self, state):
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
|
||||
# contents
|
||||
self.value = state["s"]
|
||||
|
||||
REWRITE_NODES["Str"] = StrWrapper
|
||||
|
||||
class ModuleWrapper(ast.Module):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Module, args, attrs
|
||||
|
||||
def __setstate__(self, state):
|
||||
# contents
|
||||
self.body = state["body"]
|
||||
self.type_ignores = []
|
||||
|
||||
# this is the root node, so now is a good moment do do some transforms we couldn't
|
||||
# do earlier because we weren't sure of the node type to be created.
|
||||
|
||||
transformer = AstFixupTransformer()
|
||||
transformer.visit(self)
|
||||
|
||||
REWRITE_NODES["Module"] = ModuleWrapper
|
||||
|
||||
class ReprWrapper(ast.Call):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Call, args, attrs
|
||||
|
||||
def __setstate__(self, state):
|
||||
# we need to transform `thing` into repr(thing)
|
||||
# source info
|
||||
self.lineno = state["lineno"]
|
||||
self.col_offset = state["col_offset"]
|
||||
|
||||
# contents
|
||||
self.func = ast.Name("repr", ast.Load(), lineno=self.lineno, col_offset=self.col_offset)
|
||||
self.args = [state["value"]]
|
||||
self.keywords = []
|
||||
|
||||
REWRITE_NODES["Repr"] = ReprWrapper
|
||||
|
||||
class ArgumentsWrapper(ast.arguments):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.arguments, args, attrs
|
||||
|
||||
def __setstate__(self, state):
|
||||
# source info: this node doesn't get source info
|
||||
|
||||
def make_arg(name):
|
||||
# python 2 just uses bare ast.Name nodes as arguments
|
||||
# technically it also can support more complex tuple destructuring
|
||||
# expressions in here, but python 3 just doesn't support that,
|
||||
# and there's really no good way of exactly handling that crazyness.
|
||||
assert isinstance(name, ast.Name)
|
||||
return ast.arg(name.id, lineno=name.lineno, col_offset=name.col_offset)
|
||||
|
||||
# contents. source doesn't record lineno/col_offset for vararg/kwarg
|
||||
self.posonlyargs = []
|
||||
self.args = [make_arg(i) for i in state["args"]]
|
||||
self.vararg = ast.arg(state["vararg"], lineno=1, col_offset=0)
|
||||
self.kwonlyargs = []
|
||||
self.kw_defaults = []
|
||||
self.kwarg = ast.arg(state["kwarg"], lineno=1, col_offset=0)
|
||||
self.defaults = state["defaults"]
|
||||
|
||||
REWRITE_NODES["arguments"] = ArgumentsWrapper
|
||||
|
||||
class ParamWrapper(ast.Load):
|
||||
def __reduce__(self):
|
||||
_, args, attrs = super().__reduce__()
|
||||
return ast.Load, args, attrs
|
||||
|
||||
REWRITE_NODES["Param"] = ParamWrapper
|
||||
REWRITE_NODES["Param"] = ParamWrapper
|
||||
|
||||
@@ -1503,7 +1503,11 @@ interface_layer = "screens"
|
||||
# Should Transform crop be limited to the width and height of the image being cropped?
|
||||
limit_transform_crop = False
|
||||
|
||||
# Marking labels, images and audio in replays as seen is not allowed.
|
||||
no_replay_seen = False
|
||||
|
||||
# Should we use pre-8.4 show expression behavior?
|
||||
old_show_expression = False
|
||||
|
||||
del os
|
||||
del collections
|
||||
|
||||
+1
-4
@@ -36,10 +36,7 @@ import builtins
|
||||
import io
|
||||
import time
|
||||
|
||||
if PY2:
|
||||
real_open = io.open
|
||||
else:
|
||||
real_open = builtins.open
|
||||
real_open = builtins.open
|
||||
|
||||
report = True
|
||||
|
||||
|
||||
@@ -3002,7 +3002,7 @@ class WebInput(renpy.display.displayable.Displayable):
|
||||
|
||||
@staticmethod
|
||||
def post_find_focusable():
|
||||
if PY2 or not renpy.emscripten:
|
||||
if not renpy.emscripten:
|
||||
return
|
||||
|
||||
if WebInput.active is None:
|
||||
|
||||
+5
-19
@@ -1201,24 +1201,15 @@ class Interface(object):
|
||||
renpy.config.renderer = renderer
|
||||
|
||||
if renpy.android or renpy.ios or renpy.emscripten:
|
||||
renderers = [ "gles" ]
|
||||
renderers = [ "gles2" ]
|
||||
elif renpy.windows:
|
||||
renderers = [ "gl", "angle", "gles" ]
|
||||
renderers = [ "gl2", "angle2", "gles2" ]
|
||||
else:
|
||||
renderers = [ "gl", "gles" ]
|
||||
|
||||
gl2_renderers = [ ]
|
||||
|
||||
for i in [ "gl", "angle", "gles" ]:
|
||||
|
||||
if i in renderers:
|
||||
gl2_renderers.append(i + "2")
|
||||
|
||||
renderers = gl2_renderers + renderers
|
||||
renderers = [ "gl2", "gles2" ]
|
||||
|
||||
# Prevent a performance warning if the renderer
|
||||
# is taken from old persistent data.
|
||||
if renderer not in gl2_renderers and (renpy.macintosh or renpy.android or renpy.config.gl2):
|
||||
if renderer not in renderers:
|
||||
renderer = "auto"
|
||||
|
||||
# Software renderer is the last hope for PC .
|
||||
@@ -1254,10 +1245,6 @@ class Interface(object):
|
||||
|
||||
return False
|
||||
|
||||
make_draw("gl", "renpy.gl.gldraw", "GLDraw", "gl")
|
||||
make_draw("angle", "renpy.gl.gldraw", "GLDraw", "angle")
|
||||
make_draw("gles", "renpy.gl.gldraw", "GLDraw", "gles")
|
||||
|
||||
make_draw("gl2", "renpy.gl2.gl2draw", "GL2Draw", "gl2")
|
||||
make_draw("angle2", "renpy.gl2.gl2draw", "GL2Draw", "angle2")
|
||||
make_draw("gles2", "renpy.gl2.gl2draw", "GL2Draw", "gles2")
|
||||
@@ -1364,7 +1351,6 @@ class Interface(object):
|
||||
raise Exception("Could not set video mode.")
|
||||
|
||||
renpy.session["renderer"] = draw.info["renderer"]
|
||||
renpy.game.persistent._gl2 = renpy.config.gl2
|
||||
|
||||
if renpy.android:
|
||||
android.init()
|
||||
@@ -2841,7 +2827,7 @@ class Interface(object):
|
||||
renpy.game.preferences.fullscreen = False
|
||||
|
||||
if renpy.game.preferences.fullscreen != self.fullscreen:
|
||||
if (not PY2) and renpy.emscripten:
|
||||
if renpy.emscripten:
|
||||
if renpy.game.preferences.fullscreen:
|
||||
emscripten.run_script("setFullscreen(true);")
|
||||
else:
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
# This file contains code to handle GUI-based error reporting.
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
import os
|
||||
@@ -60,8 +60,6 @@ def init_display():
|
||||
The minimum amount of code required to init the display.
|
||||
"""
|
||||
|
||||
renpy.config.gl2 = getattr(renpy.game.persistent, "_gl2", True)
|
||||
|
||||
# Ensure we have correctly-typed preferences.
|
||||
renpy.game.preferences.check()
|
||||
|
||||
@@ -145,7 +143,7 @@ def report_exception(short, full, traceback_fn):
|
||||
reload_action=reload_action,
|
||||
ignore_action=ignore_action,
|
||||
traceback_fn=traceback_fn,
|
||||
)
|
||||
)
|
||||
|
||||
renpy.display.im.ignored_images |= renpy.display.im.images_to_ignore
|
||||
|
||||
@@ -164,7 +162,7 @@ def report_exception(short, full, traceback_fn):
|
||||
raise
|
||||
|
||||
|
||||
def report_parse_errors(errors, error_fn):
|
||||
def report_parse_errors(errors: list[str], error_fn: str) -> bool:
|
||||
"""
|
||||
Reports an exception to the user. Returns True if the exception should
|
||||
be raised by the normal reporting mechanisms. Otherwise, should raise
|
||||
@@ -176,7 +174,7 @@ def report_parse_errors(errors, error_fn):
|
||||
|
||||
error_dump()
|
||||
|
||||
if renpy.game.args.command != "run": # @UndefinedVariable
|
||||
if renpy.game.args.command != "run":
|
||||
return True
|
||||
|
||||
if "RENPY_SIMPLE_EXCEPTIONS" in os.environ:
|
||||
@@ -201,7 +199,7 @@ def report_parse_errors(errors, error_fn):
|
||||
reload_action=reload_action,
|
||||
errors=errors,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
)
|
||||
|
||||
except renpy.game.CONTROL_EXCEPTIONS:
|
||||
raise
|
||||
@@ -210,3 +208,5 @@ def report_parse_errors(errors, error_fn):
|
||||
renpy.display.log.write("While handling exception:")
|
||||
renpy.display.log.exception()
|
||||
raise
|
||||
|
||||
return False
|
||||
|
||||
@@ -407,10 +407,14 @@ class ImageReference(renpy.display.displayable.Displayable):
|
||||
|
||||
if not name:
|
||||
error("Image '%s' not found." % ' '.join(self.name))
|
||||
if renpy.game.lint:
|
||||
renpy.lint.report("References image '%s', which does not exist.", ' '.join(self.name))
|
||||
return False
|
||||
|
||||
if name and (self._args.name == name):
|
||||
error("Image '{}' refers to itself.".format(' '.join(name)))
|
||||
if renpy.game.lint:
|
||||
renpy.lint.report("Image '%s' refers to itself.", ' '.join(name))
|
||||
return False
|
||||
|
||||
args += self._args.args
|
||||
|
||||
@@ -31,11 +31,7 @@ import renpy
|
||||
from renpy.display.render import render, Render
|
||||
|
||||
|
||||
if PY2:
|
||||
def compute_raw(value, room):
|
||||
return renpy.display.core.absolute.compute_raw(value, room)
|
||||
else:
|
||||
compute_raw = renpy.display.core.absolute.compute_raw
|
||||
compute_raw = renpy.display.core.absolute.compute_raw
|
||||
|
||||
|
||||
def xyminimums(style, width, height):
|
||||
|
||||
@@ -248,14 +248,12 @@ def progress(kind, done, total):
|
||||
if done == total:
|
||||
return
|
||||
|
||||
if not PY2:
|
||||
if progress_kind != kind:
|
||||
print()
|
||||
print(kind)
|
||||
progress_kind = kind
|
||||
sys.stdout.flush()
|
||||
|
||||
if progress_kind != kind:
|
||||
print()
|
||||
print(kind)
|
||||
progress_kind = kind
|
||||
sys.stdout.flush()
|
||||
|
||||
emscripten.run_script(r"""progress(%d, %d);""" % (done, total))
|
||||
emscripten.run_script(r"""progress(%d, %d);""" % (done, total))
|
||||
|
||||
emscripten.sleep(0)
|
||||
|
||||
@@ -979,6 +979,41 @@ class SceneLists(renpy.object.Object):
|
||||
sl.sort(key=lambda sle : sle.zorder)
|
||||
|
||||
|
||||
class _HasTransforms:
|
||||
"""
|
||||
This is returned from layer_has_transforms.
|
||||
"""
|
||||
|
||||
at_list : bool
|
||||
camera: bool
|
||||
config_layer_transforms: bool
|
||||
|
||||
def layer_has_transforms(layer):
|
||||
"""
|
||||
:doc: undocumented
|
||||
|
||||
Used to determine if a layer has transforms associated with it. Returns
|
||||
an object with the following attributes:
|
||||
|
||||
at_list
|
||||
True unless the at_list is empty.
|
||||
|
||||
camera
|
||||
True unless the camera list is empty.
|
||||
|
||||
config_layer_transforms
|
||||
True unless the config.layer_transforms list is empty.
|
||||
"""
|
||||
|
||||
rv = _HasTransforms()
|
||||
|
||||
rv.at_list = bool(scene_lists().layer_at_list[layer][1])
|
||||
rv.camera = bool(scene_lists().camera_list[layer][1])
|
||||
rv.config_layer_transforms = bool(renpy.config.layer_transforms.get(layer, [ ]))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def scene_lists(index=-1):
|
||||
"""
|
||||
Returns either the current scenelists object, or the one for the
|
||||
|
||||
@@ -606,6 +606,14 @@ class TransformState(renpy.object.Object):
|
||||
xycenter = property(get_pos, set_xycenter)
|
||||
|
||||
|
||||
def simplify_position(v):
|
||||
if isinstance(v, tuple):
|
||||
return tuple(simplify_position(i) for i in v)
|
||||
elif isinstance(v, position):
|
||||
return v.simplify()
|
||||
else:
|
||||
return v
|
||||
|
||||
class Proxy(object):
|
||||
"""
|
||||
This class proxies a field from the transform to its state.
|
||||
@@ -616,14 +624,6 @@ class Proxy(object):
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
|
||||
def simplify_position(v):
|
||||
if isinstance(v, tuple):
|
||||
return tuple(simplify_position(i) for i in v)
|
||||
elif isinstance(v, position):
|
||||
return v.simplify()
|
||||
else:
|
||||
return v
|
||||
|
||||
return simplify_position(getattr(instance.state, self.name))
|
||||
|
||||
def __set__(self, instance, value):
|
||||
|
||||
+2
-6
@@ -249,12 +249,8 @@ def dump(error):
|
||||
if filename != "-":
|
||||
new = filename + ".new"
|
||||
|
||||
if PY2:
|
||||
with open(new, "wb") as f:
|
||||
json.dump(result, f) # type: ignore
|
||||
else:
|
||||
with open(new, "w") as f:
|
||||
json.dump(result, f)
|
||||
with open(new, "w") as f:
|
||||
json.dump(result, f)
|
||||
|
||||
if os.path.exists(filename):
|
||||
os.unlink(filename)
|
||||
|
||||
+2
-4
@@ -34,10 +34,8 @@ import renpy
|
||||
Color = renpy.color.Color
|
||||
color = renpy.color.Color
|
||||
|
||||
if PY2:
|
||||
from collections import Iterable # type: ignore
|
||||
else:
|
||||
from collections.abc import Iterable
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
|
||||
def lookup_displayable_prefix(d):
|
||||
|
||||
+3
-2
@@ -654,8 +654,9 @@ class Context(renpy.object.Object):
|
||||
renpy.store._kwargs = e.kwargs
|
||||
|
||||
if self.seen:
|
||||
renpy.game.persistent._seen_ever[self.current] = True # type: ignore
|
||||
renpy.game.seen_session[self.current] = True
|
||||
if renpy.exports.is_seen_allowed():
|
||||
renpy.game.persistent._seen_ever[self.current] = True # type: ignore
|
||||
renpy.game.seen_session[self.current] = True
|
||||
|
||||
renpy.plog(2, " end {} ({}:{})", type_node_name, this_node.filename, this_node.linenumber)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import pygame_sdl2
|
||||
try:
|
||||
import emscripten
|
||||
except ImportError:
|
||||
pass
|
||||
emscripten = None
|
||||
|
||||
import renpy.audio.sound as sound
|
||||
import renpy.audio.music as music
|
||||
@@ -113,6 +113,10 @@ from renpy.display.predict import (
|
||||
screen as predict_screen,
|
||||
)
|
||||
|
||||
from renpy.display.scenelists import (
|
||||
layer_has_transforms,
|
||||
)
|
||||
|
||||
from renpy.display.screen import (
|
||||
ScreenProfile as profile_screen,
|
||||
current_screen,
|
||||
@@ -484,6 +488,7 @@ from renpy.exports.persistentexports import (
|
||||
mark_image_unseen,
|
||||
save_persistent,
|
||||
is_seen,
|
||||
is_seen_allowed,
|
||||
)
|
||||
|
||||
from renpy.exports.platformexports import (
|
||||
|
||||
@@ -492,7 +492,8 @@ def show(name, at_list=[ ], layer=None, what=None, zorder=None, tag=None, behind
|
||||
img._unique()
|
||||
|
||||
# Update the list of images we have ever seen.
|
||||
renpy.game.persistent._seen_images[tuple(str(i) for i in name)] = True
|
||||
if renpy.exports.is_seen_allowed():
|
||||
renpy.game.persistent._seen_images[tuple(str(i) for i in name)] = True
|
||||
|
||||
if tag and munge_name:
|
||||
name = (tag,) + name[1:]
|
||||
@@ -1278,9 +1279,6 @@ def get_refresh_rate(precision=5):
|
||||
to 1 disables this.
|
||||
"""
|
||||
|
||||
if PY2:
|
||||
precision = float(precision)
|
||||
|
||||
info = renpy.display.get_info()
|
||||
rv = info.refresh_rate # type: ignore
|
||||
rv = round(rv / precision) * precision
|
||||
|
||||
@@ -34,18 +34,11 @@ except ImportError:
|
||||
|
||||
import renpy
|
||||
|
||||
if PY2:
|
||||
from urllib import urlencode as _urlencode # type: ignore
|
||||
else:
|
||||
from urllib.parse import urlencode as _urlencode
|
||||
from urllib.parse import urlencode as _urlencode
|
||||
|
||||
try:
|
||||
if PY2:
|
||||
import urllib
|
||||
proxies = urllib.getproxies() # type: ignore
|
||||
else:
|
||||
import urllib.request
|
||||
proxies = urllib.request.getproxies()
|
||||
import urllib.request
|
||||
proxies = urllib.request.getproxies()
|
||||
except Exception as e:
|
||||
proxies = {}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ def input(prompt, default='', allow=None, exclude='{}', length=None, with_none=N
|
||||
|
||||
fixed = renpy.exports.in_fixed_rollback()
|
||||
|
||||
if (not PY2) and renpy.emscripten and renpy.config.web_input and not fixed:
|
||||
if renpy.emscripten and renpy.config.web_input and not fixed:
|
||||
return web_input(prompt, default, allow, exclude, length, bool(mask))
|
||||
|
||||
renpy.exports.mode('input')
|
||||
|
||||
@@ -164,7 +164,7 @@ def fsencode(s, force=False): # type: (str, bool) -> str
|
||||
Converts s from unicode to the filesystem encoding.
|
||||
"""
|
||||
|
||||
if (not PY2) and (not force):
|
||||
if not force:
|
||||
return s
|
||||
|
||||
if not isinstance(s, str):
|
||||
|
||||
@@ -161,3 +161,13 @@ def is_seen(ever=True):
|
||||
"""
|
||||
|
||||
return renpy.game.context().seen_current(ever)
|
||||
|
||||
|
||||
def is_seen_allowed():
|
||||
"""
|
||||
:doc: other
|
||||
|
||||
Returns False only if in a replay and no seen events allowed by setting :var:`config.no_replay_seen` to True.
|
||||
"""
|
||||
|
||||
return not (renpy.store._in_replay and renpy.config.no_replay_seen)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
|
||||
import renpy
|
||||
renpy.update_path()
|
||||
|
||||
|
||||
# Generated by scripts/relative_imports.py, do not edit below this line.
|
||||
if 1 == 0:
|
||||
from . import gldraw
|
||||
from . import glenviron_shader
|
||||
from . import glfunctions
|
||||
from . import glrtt_copy
|
||||
from . import glrtt_fbo
|
||||
from . import gltexture
|
||||
@@ -1,96 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from renpy.display.matrix cimport Matrix, Matrix2D
|
||||
cimport renpy.display.render as render
|
||||
|
||||
cdef class Environ
|
||||
|
||||
cdef class GLDraw:
|
||||
|
||||
cdef bint gles
|
||||
cdef bint angle
|
||||
cdef bint did_init
|
||||
cdef bint did_texture_test
|
||||
cdef Environ environ
|
||||
cdef public object rtt
|
||||
cdef object window
|
||||
cdef tuple virtual_size
|
||||
cdef public tuple physical_size
|
||||
cdef public tuple drawable_size
|
||||
cdef public tuple virtual_box
|
||||
cdef public tuple physical_box
|
||||
cdef object texture_cache
|
||||
cdef double last_redraw_time
|
||||
cdef double redraw_period
|
||||
cdef public dict info
|
||||
cdef object old_fullscreen
|
||||
cdef public object fullscreen_surface
|
||||
cdef object display_info
|
||||
cdef tuple clip_cache
|
||||
cdef bint fast_dissolve
|
||||
cdef tuple default_clip
|
||||
cdef bint did_render_to_texture
|
||||
cdef float dpi_scale
|
||||
cdef object ready_texture_queue
|
||||
|
||||
cdef public tuple clip_rtt_box
|
||||
|
||||
# The number of drawable pixels per virtual pixel.
|
||||
cdef public object draw_per_virt
|
||||
|
||||
# Matrices that transform drawable to virtual, and vice versa.
|
||||
cdef public Matrix virt_to_draw
|
||||
cdef public Matrix draw_to_virt
|
||||
|
||||
cdef public int fast_redraw_frames
|
||||
|
||||
cpdef set_clip(GLDraw self, tuple clip)
|
||||
|
||||
cpdef int draw_render_textures(
|
||||
GLDraw self,
|
||||
object what,
|
||||
bint non_aligned) except 1
|
||||
|
||||
cpdef int draw_transformed(
|
||||
GLDraw self,
|
||||
object what,
|
||||
tuple clip,
|
||||
double xo,
|
||||
double yo,
|
||||
double alpha,
|
||||
double over,
|
||||
Matrix reverse,
|
||||
bint nearest,
|
||||
bint subpixel) except 1
|
||||
|
||||
cdef class Environ:
|
||||
cdef void blit(self)
|
||||
cdef void blend(self, double fraction)
|
||||
cdef void imageblend(self, double fraction, int ramp)
|
||||
cdef void set_vertex(self, float *vertices)
|
||||
cdef void set_texture(self, int unit, float *coords)
|
||||
cdef void set_color(self, float r, float g, float b, float a)
|
||||
cdef void set_clip(self, tuple clip_box, GLDraw draw)
|
||||
cdef void unset_clip(self, GLDraw draw)
|
||||
cdef void ortho(self, double left, double right, double bottom, double top, double near, double far)
|
||||
cdef void viewport(self, int x, int y, int width, int height)
|
||||
|
||||
-1417
File diff suppressed because it is too large
Load Diff
@@ -1,671 +0,0 @@
|
||||
#cython: profile=False
|
||||
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from renpy.uguu.gl cimport *
|
||||
from renpy.gl.gldraw cimport *
|
||||
|
||||
cdef int round(double d):
|
||||
return <int> (d + .5)
|
||||
|
||||
|
||||
GLES_PORTABILITY = """\
|
||||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
# else
|
||||
precision mediump float;
|
||||
# endif
|
||||
#endif
|
||||
"""
|
||||
|
||||
VERTEX_SHADER1 = GLES_PORTABILITY + """\
|
||||
uniform mat4 Projection;
|
||||
|
||||
attribute vec4 Vertex;
|
||||
attribute vec2 VertexTexCoord0;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
|
||||
varying vec2 pos;
|
||||
|
||||
void main() {
|
||||
TexCoord0 = VertexTexCoord0;
|
||||
|
||||
pos = Vertex.xy;
|
||||
gl_Position = Projection * Vertex;
|
||||
}
|
||||
"""
|
||||
|
||||
VERTEX_SHADER2 = GLES_PORTABILITY + """\
|
||||
uniform mat4 Projection;
|
||||
|
||||
attribute vec4 Vertex;
|
||||
attribute vec2 VertexTexCoord0;
|
||||
attribute vec2 VertexTexCoord1;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
varying vec2 TexCoord1;
|
||||
|
||||
varying vec2 pos;
|
||||
|
||||
void main() {
|
||||
TexCoord0 = VertexTexCoord0;
|
||||
TexCoord1 = VertexTexCoord1;
|
||||
|
||||
pos = Vertex.xy;
|
||||
gl_Position = Projection * Vertex;
|
||||
}
|
||||
"""
|
||||
|
||||
VERTEX_SHADER3 = GLES_PORTABILITY + """\
|
||||
uniform mat4 Projection;
|
||||
|
||||
attribute vec4 Vertex;
|
||||
attribute vec2 VertexTexCoord0;
|
||||
attribute vec2 VertexTexCoord1;
|
||||
attribute vec2 VertexTexCoord2;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
varying vec2 TexCoord1;
|
||||
varying vec2 TexCoord2;
|
||||
|
||||
varying vec2 pos;
|
||||
|
||||
void main() {
|
||||
TexCoord0 = VertexTexCoord0;
|
||||
TexCoord1 = VertexTexCoord1;
|
||||
TexCoord2 = VertexTexCoord2;
|
||||
|
||||
pos = Vertex.xy;
|
||||
gl_Position = Projection * Vertex;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
BLIT_SHADER = GLES_PORTABILITY + """\
|
||||
uniform vec4 Color;
|
||||
uniform sampler2D tex0;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
|
||||
varying vec2 pos;
|
||||
uniform vec2 clip0;
|
||||
uniform vec2 clip1;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color0 = texture2D(tex0, TexCoord0.st);
|
||||
gl_FragColor = color0 * Color;
|
||||
}
|
||||
"""
|
||||
|
||||
BLIT_CLIP_SHADER = GLES_PORTABILITY + """\
|
||||
uniform vec4 Color;
|
||||
uniform sampler2D tex0;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
|
||||
varying vec2 pos;
|
||||
uniform vec2 clip0;
|
||||
uniform vec2 clip1;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (pos.x < clip0.x || pos.y < clip0.y || pos.x >= clip1.x || pos.y >= clip1.y) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec4 color0 = texture2D(tex0, TexCoord0.st);
|
||||
gl_FragColor = color0 * Color;
|
||||
}
|
||||
"""
|
||||
|
||||
BLEND_SHADER = GLES_PORTABILITY + """\
|
||||
uniform vec4 Color;
|
||||
uniform sampler2D tex0;
|
||||
uniform sampler2D tex1;
|
||||
uniform float done;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
varying vec2 TexCoord1;
|
||||
|
||||
varying vec2 pos;
|
||||
uniform vec2 clip0;
|
||||
uniform vec2 clip1;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color0 = texture2D(tex0, TexCoord0.st);
|
||||
vec4 color1 = texture2D(tex1, TexCoord1.st);
|
||||
|
||||
gl_FragColor = mix(color0, color1, done) * Color;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
BLEND_CLIP_SHADER = GLES_PORTABILITY + """\
|
||||
uniform vec4 Color;
|
||||
uniform sampler2D tex0;
|
||||
uniform sampler2D tex1;
|
||||
uniform float done;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
varying vec2 TexCoord1;
|
||||
|
||||
varying vec2 pos;
|
||||
uniform vec2 clip0;
|
||||
uniform vec2 clip1;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (pos.x < clip0.x || pos.y < clip0.y || pos.x >= clip1.x || pos.y >= clip1.y) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec4 color0 = texture2D(tex0, TexCoord0.st);
|
||||
vec4 color1 = texture2D(tex1, TexCoord1.st);
|
||||
|
||||
gl_FragColor = mix(color0, color1, done) * Color;
|
||||
}
|
||||
"""
|
||||
|
||||
IMAGEBLEND_SHADER = GLES_PORTABILITY + """\
|
||||
uniform vec4 Color;
|
||||
uniform sampler2D tex0;
|
||||
uniform sampler2D tex1;
|
||||
uniform sampler2D tex2;
|
||||
uniform float offset;
|
||||
uniform float multiplier;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
varying vec2 TexCoord1;
|
||||
varying vec2 TexCoord2;
|
||||
|
||||
varying vec2 pos;
|
||||
uniform vec2 clip0;
|
||||
uniform vec2 clip1;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color0 = texture2D(tex0, TexCoord0.st);
|
||||
vec4 color1 = texture2D(tex1, TexCoord1.st);
|
||||
vec4 color2 = texture2D(tex2, TexCoord2.st);
|
||||
|
||||
float a = clamp((color0.a + offset) * multiplier, 0.0, 1.0);
|
||||
|
||||
gl_FragColor = mix(color1, color2, a) * Color;
|
||||
}
|
||||
"""
|
||||
|
||||
IMAGEBLEND_CLIP_SHADER = GLES_PORTABILITY + """\
|
||||
uniform vec4 Color;
|
||||
uniform sampler2D tex0;
|
||||
uniform sampler2D tex1;
|
||||
uniform sampler2D tex2;
|
||||
uniform float offset;
|
||||
uniform float multiplier;
|
||||
|
||||
varying vec2 TexCoord0;
|
||||
varying vec2 TexCoord1;
|
||||
varying vec2 TexCoord2;
|
||||
|
||||
varying vec2 pos;
|
||||
uniform vec2 clip0;
|
||||
uniform vec2 clip1;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (pos.x < clip0.x || pos.y < clip0.y || pos.x >= clip1.x || pos.y >= clip1.y) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec4 color0 = texture2D(tex0, TexCoord0.st);
|
||||
vec4 color1 = texture2D(tex1, TexCoord1.st);
|
||||
vec4 color2 = texture2D(tex2, TexCoord2.st);
|
||||
|
||||
float a = clamp((color0.a + offset) * multiplier, 0.0, 1.0);
|
||||
|
||||
gl_FragColor = mix(color1, color2, a) * Color;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
def check_status(shader, handle, type):
|
||||
"""
|
||||
Checks the status of a shader or program. If it fails, then an
|
||||
exception is raised.
|
||||
"""
|
||||
|
||||
cdef GLint status = 0
|
||||
cdef GLint log_length = 0
|
||||
|
||||
if shader:
|
||||
glGetShaderiv(handle, type, &status)
|
||||
else:
|
||||
glGetProgramiv(handle, type, &status)
|
||||
|
||||
if status == 1: # 0 for problems.
|
||||
return
|
||||
|
||||
if shader:
|
||||
glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length)
|
||||
else:
|
||||
glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length)
|
||||
|
||||
log = ' ' * log_length
|
||||
|
||||
if shader:
|
||||
glGetShaderInfoLog(handle, log_length, &log_length, <char *> log)
|
||||
else:
|
||||
glGetProgramInfoLog(handle, log_length, &log_length, <char *> log)
|
||||
|
||||
raise Exception("Shader error: %s" % log)
|
||||
|
||||
|
||||
def compile_shader(kind, source):
|
||||
"""
|
||||
Allocates and compiles a shader.
|
||||
"""
|
||||
|
||||
source = source.encode("utf-8")
|
||||
|
||||
cdef char *sourceptr = <char *> source
|
||||
cdef int lensource = len(source)
|
||||
|
||||
handle = glCreateShader(kind)
|
||||
glShaderSource(handle, 1, <const GLchar * const *> &sourceptr, &lensource)
|
||||
glCompileShader(handle)
|
||||
|
||||
check_status(True, handle, GL_COMPILE_STATUS)
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def compile_program(vertex, fragment):
|
||||
"""
|
||||
Compiles a pair of shaders into a program.
|
||||
"""
|
||||
|
||||
vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex)
|
||||
fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment)
|
||||
|
||||
program = glCreateProgram()
|
||||
|
||||
glAttachShader(program, vertex_shader)
|
||||
glAttachShader(program, fragment_shader)
|
||||
|
||||
glLinkProgram(program)
|
||||
|
||||
check_status(False, program, GL_LINK_STATUS)
|
||||
|
||||
glUseProgram(program)
|
||||
|
||||
glDeleteShader(vertex_shader)
|
||||
glDeleteShader(fragment_shader)
|
||||
|
||||
return program
|
||||
|
||||
cdef class Program(object):
|
||||
"""
|
||||
Encapsulates a program.
|
||||
"""
|
||||
|
||||
cdef GLuint program
|
||||
|
||||
# Attributes.
|
||||
cdef GLint Vertex
|
||||
cdef GLint VertexTexCoord0
|
||||
cdef GLint VertexTexCoord1
|
||||
cdef GLint VertexTexCoord2
|
||||
|
||||
# Uniforms.
|
||||
cdef GLint Projection
|
||||
cdef GLint Color
|
||||
|
||||
cdef GLint tex0
|
||||
cdef GLint tex1
|
||||
cdef GLint tex2
|
||||
|
||||
cdef GLint clip0
|
||||
cdef GLint clip1
|
||||
|
||||
cdef GLint offset
|
||||
cdef GLint multiplier
|
||||
cdef GLint done
|
||||
|
||||
def __init__(self, vertex, fragment):
|
||||
self.program = compile_program(vertex, fragment)
|
||||
|
||||
self.Vertex = glGetAttribLocation(self.program, "Vertex")
|
||||
self.VertexTexCoord0 = glGetAttribLocation(self.program, "VertexTexCoord0")
|
||||
self.VertexTexCoord1 = glGetAttribLocation(self.program, "VertexTexCoord1")
|
||||
self.VertexTexCoord2 = glGetAttribLocation(self.program, "VertexTexCoord2")
|
||||
|
||||
self.Projection = glGetUniformLocation(self.program, "Projection")
|
||||
self.tex0 = glGetUniformLocation(self.program, "tex0")
|
||||
self.tex1 = glGetUniformLocation(self.program, "tex1")
|
||||
self.tex2 = glGetUniformLocation(self.program, "tex2")
|
||||
self.offset = glGetUniformLocation(self.program, "offset")
|
||||
self.multiplier = glGetUniformLocation(self.program, "multiplier")
|
||||
self.done = glGetUniformLocation(self.program, "done")
|
||||
self.Color = glGetUniformLocation(self.program, "Color")
|
||||
self.clip0 = glGetUniformLocation(self.program, "clip0")
|
||||
self.clip1 = glGetUniformLocation(self.program, "clip1")
|
||||
|
||||
def disable_attribs(self):
|
||||
# Disable the vertex attributes used by this program.
|
||||
|
||||
if self.Vertex != -1:
|
||||
glDisableVertexAttribArray(self.Vertex)
|
||||
|
||||
if self.VertexTexCoord0 != -1:
|
||||
glDisableVertexAttribArray(self.VertexTexCoord0)
|
||||
|
||||
if self.VertexTexCoord1 != -1:
|
||||
glDisableVertexAttribArray(self.VertexTexCoord1)
|
||||
|
||||
if self.VertexTexCoord2 != -1:
|
||||
glDisableVertexAttribArray(self.VertexTexCoord2)
|
||||
|
||||
def delete(self):
|
||||
glDeleteProgram(self.program)
|
||||
|
||||
|
||||
cdef class ShaderEnviron(Environ):
|
||||
"""
|
||||
This is an environment that uses shaders.
|
||||
"""
|
||||
|
||||
cdef Program program
|
||||
cdef float projection[16]
|
||||
|
||||
cdef Program blit_program
|
||||
cdef Program blend_program
|
||||
cdef Program imageblend_program
|
||||
|
||||
cdef Program blit_clip_program
|
||||
cdef Program blend_clip_program
|
||||
cdef Program imageblend_clip_program
|
||||
|
||||
cdef bint clipping
|
||||
cdef double clip_x0
|
||||
cdef double clip_y0
|
||||
cdef double clip_x1
|
||||
cdef double clip_y1
|
||||
|
||||
cdef int viewport_x
|
||||
cdef int viewport_y
|
||||
cdef int viewport_w
|
||||
cdef int viewport_h
|
||||
|
||||
|
||||
def init(self):
|
||||
|
||||
self.blit_program = Program(VERTEX_SHADER1, BLIT_SHADER)
|
||||
self.blit_clip_program = Program(VERTEX_SHADER1, BLIT_CLIP_SHADER)
|
||||
|
||||
self.blend_program = Program(VERTEX_SHADER2, BLEND_SHADER)
|
||||
self.blend_clip_program = Program(VERTEX_SHADER2, BLEND_CLIP_SHADER)
|
||||
|
||||
self.imageblend_program = Program(VERTEX_SHADER3, IMAGEBLEND_SHADER)
|
||||
self.imageblend_clip_program = Program(VERTEX_SHADER3, IMAGEBLEND_CLIP_SHADER)
|
||||
|
||||
# The current program.
|
||||
self.program = None
|
||||
|
||||
def deinit(self):
|
||||
"""
|
||||
Called before changing the GL context.
|
||||
"""
|
||||
|
||||
if self.program is not None:
|
||||
self.program.disable_attribs()
|
||||
self.program = None
|
||||
|
||||
self.blit_program.delete()
|
||||
self.blend_program.delete()
|
||||
self.imageblend_program.delete()
|
||||
|
||||
def activate(self, Program program):
|
||||
|
||||
if self.program is not None:
|
||||
self.program.disable_attribs()
|
||||
|
||||
self.program = program
|
||||
|
||||
glUseProgram(program.program)
|
||||
glUniformMatrix4fv(program.Projection, 1, GL_FALSE, self.projection)
|
||||
|
||||
if self.clipping:
|
||||
glUniform2f(program.clip0, self.clip_x0 - .01, self.clip_y0 - .01)
|
||||
glUniform2f(program.clip1, self.clip_x1 + .01, self.clip_y1 + .01)
|
||||
|
||||
cdef void blit(self):
|
||||
|
||||
if self.clipping:
|
||||
program = self.blit_clip_program
|
||||
else:
|
||||
program = self.blit_program
|
||||
|
||||
if self.program is not program:
|
||||
self.activate(program)
|
||||
glUniform1i(program.tex0, 0)
|
||||
|
||||
cdef void blend(self, double fraction):
|
||||
if self.clipping:
|
||||
program = self.blend_clip_program
|
||||
else:
|
||||
program = self.blend_program
|
||||
|
||||
if self.program is not program:
|
||||
self.activate(program)
|
||||
glUniform1i(program.tex0, 0)
|
||||
glUniform1i(program.tex1, 1)
|
||||
|
||||
glUniform1f(program.done, fraction)
|
||||
|
||||
cdef void imageblend(self, double fraction, int ramp):
|
||||
|
||||
if self.clipping:
|
||||
program = self.imageblend_clip_program
|
||||
else:
|
||||
program = self.imageblend_program
|
||||
|
||||
if self.program is not program:
|
||||
self.activate(program)
|
||||
glUniform1i(program.tex0, 0)
|
||||
glUniform1i(program.tex1, 1)
|
||||
glUniform1i(program.tex2, 2)
|
||||
|
||||
# Prevent a DBZ if the user gives us a 0 ramp.
|
||||
if ramp < 1:
|
||||
ramp = 1
|
||||
|
||||
# Compute the offset to apply to the alpha.
|
||||
start = -1.0
|
||||
end = ramp / 256.0
|
||||
offset = start + ( end - start) * fraction
|
||||
|
||||
# Setup the multiplier and the offset.
|
||||
glUniform1f(program.multiplier, 256.0 / ramp)
|
||||
glUniform1f(program.offset, offset)
|
||||
|
||||
cdef void set_vertex(self, float *vertices):
|
||||
glEnableVertexAttribArray(self.program.Vertex)
|
||||
glVertexAttribPointer(self.program.Vertex, 2, GL_FLOAT, GL_FALSE, 0, <GLubyte *> vertices)
|
||||
|
||||
cdef void set_texture(self, int unit, float *coords):
|
||||
cdef tex
|
||||
|
||||
if unit == 0:
|
||||
tex = self.program.VertexTexCoord0
|
||||
elif unit == 1:
|
||||
tex = self.program.VertexTexCoord1
|
||||
elif unit == 2:
|
||||
tex = self.program.VertexTexCoord2
|
||||
else:
|
||||
return
|
||||
|
||||
if tex < 0:
|
||||
return
|
||||
|
||||
if coords != NULL:
|
||||
glVertexAttribPointer(tex, 2, GL_FLOAT, GL_FALSE, 0, <GLubyte *> coords)
|
||||
glEnableVertexAttribArray(tex)
|
||||
else:
|
||||
glDisableVertexAttribArray(tex)
|
||||
|
||||
cdef void set_color(self, float r, float g, float b, float a):
|
||||
glUniform4f(self.program.Color, r, g, b, a)
|
||||
|
||||
cdef void ortho(self, double left, double right, double bottom, double top, double near, double far):
|
||||
|
||||
self.projection[ 0] = 2 / (right - left)
|
||||
self.projection[ 4] = 0
|
||||
self.projection[ 8] = 0
|
||||
self.projection[12] = -(right + left) / (right - left)
|
||||
|
||||
self.projection[ 1] = 0
|
||||
self.projection[ 5] = 2 / (top - bottom)
|
||||
self.projection[ 9] = 0
|
||||
self.projection[13] = -(top + bottom) / (top - bottom)
|
||||
|
||||
self.projection[ 2] = 0
|
||||
self.projection[ 6] = 0
|
||||
self.projection[10] = -2 / (far - near)
|
||||
self.projection[14] = -(far + near) / (far - near)
|
||||
|
||||
self.projection[ 3] = 0
|
||||
self.projection[ 7] = 0
|
||||
self.projection[11] = 0
|
||||
self.projection[15] = 1
|
||||
|
||||
if self.program is not None:
|
||||
self.program.disable_attribs()
|
||||
|
||||
self.program = None
|
||||
|
||||
cdef void project(self, x, y, z, double *rv_x, double *rv_y, double *rv_z):
|
||||
"""
|
||||
Given a point, projects it using the projection.
|
||||
"""
|
||||
|
||||
rv_x[0] = x * self.projection[0] + y * self.projection[4] + z * self.projection[8] + self.projection[12]
|
||||
rv_y[0] = x * self.projection[1] + y * self.projection[5] + z * self.projection[9] + self.projection[13]
|
||||
rv_z[0] = x * self.projection[2] + y * self.projection[6] + z * self.projection[10] + self.projection[14]
|
||||
|
||||
cdef void set_clip(self, tuple clip_box, GLDraw draw):
|
||||
|
||||
cdef double minx, miny, maxx, maxy, z
|
||||
cdef double vwidth, vheight
|
||||
cdef double px, py, pw, ph
|
||||
cdef int cx, cy, cw, ch
|
||||
cdef int psw, psh
|
||||
|
||||
if clip_box == draw.default_clip:
|
||||
self.unset_clip(draw)
|
||||
return
|
||||
|
||||
minx, miny, maxx, maxy = clip_box
|
||||
psw, psh = draw.physical_size
|
||||
|
||||
# The clipping box.
|
||||
self.clipping = True
|
||||
self.clip_x0 = minx
|
||||
self.clip_y0 = miny
|
||||
self.clip_x1 = maxx
|
||||
self.clip_y1 = maxy
|
||||
|
||||
# Set the scissor rectangle to be slightly larger than the
|
||||
# clipping box. This ensures everything that needs to be drawn
|
||||
# is drawn, and we don't spend a lot of time shading clipped
|
||||
# fragments.
|
||||
|
||||
if draw.clip_rtt_box is None:
|
||||
|
||||
z = 0
|
||||
|
||||
# Project to normalized coordinates.
|
||||
self.project(minx, miny, z, &minx, &miny, &z)
|
||||
self.project(maxx, maxy, z, &maxx, &maxy, &z)
|
||||
|
||||
# Convert to window coordinates.
|
||||
minx = (minx + 1) * self.viewport_w / 2 + self.viewport_x
|
||||
maxx = (maxx + 1) * self.viewport_w / 2 + self.viewport_x
|
||||
miny = (miny + 1) * self.viewport_h / 2 + self.viewport_y
|
||||
maxy = (maxy + 1) * self.viewport_h / 2 + self.viewport_y
|
||||
|
||||
# Increase the bounding box, to ensure every relevant pixel is
|
||||
# in it. The shader will take care of enforcing the actual box.
|
||||
minx -= 1
|
||||
maxx += 1
|
||||
miny += 1
|
||||
maxy -= 1
|
||||
|
||||
if minx < 0:
|
||||
minx = 0
|
||||
if miny < 0:
|
||||
miny = 0
|
||||
|
||||
glEnable(GL_SCISSOR_TEST)
|
||||
glScissor(<GLint> round(minx), <GLint> round(maxy), <GLint> round(maxx - minx), <GLsizei> round(miny - maxy))
|
||||
|
||||
else:
|
||||
|
||||
cx, cy, cw, ch = draw.clip_rtt_box
|
||||
|
||||
glEnable(GL_SCISSOR_TEST)
|
||||
glScissor(<GLint> round(minx - cx), <GLint> round(miny - cy), <GLint> round(maxx - minx), <GLint> round(maxy - miny))
|
||||
|
||||
if self.program is not None:
|
||||
self.program.disable_attribs()
|
||||
|
||||
self.program = None
|
||||
|
||||
cdef void unset_clip(self, GLDraw draw):
|
||||
|
||||
glDisable(GL_SCISSOR_TEST)
|
||||
|
||||
self.clipping = False
|
||||
self.clip_x0 = 0
|
||||
self.clip_y0 = 0
|
||||
self.clip_x1 = 65535
|
||||
self.clip_y1 = 65535
|
||||
|
||||
if self.program is not None:
|
||||
self.program.disable_attribs()
|
||||
|
||||
self.program = None
|
||||
|
||||
cdef void viewport(self, int x, int y, int width, int height):
|
||||
glViewport(x, y, width, height)
|
||||
self.viewport_x = x
|
||||
self.viewport_y = y
|
||||
self.viewport_w = width
|
||||
self.viewport_h = height
|
||||
@@ -1,59 +0,0 @@
|
||||
# This file has been automatically generated by module/uguu/generate_required_functions.py,
|
||||
# please do not edit it by hand.
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
|
||||
required_functions = [
|
||||
"glActiveTexture",
|
||||
"glAttachShader",
|
||||
"glBindFramebuffer",
|
||||
"glBindTexture",
|
||||
"glBlendFunc",
|
||||
"glClear",
|
||||
"glClearColor",
|
||||
"glCompileShader",
|
||||
"glCopyTexImage2D",
|
||||
"glCopyTexSubImage2D",
|
||||
"glCreateProgram",
|
||||
"glCreateShader",
|
||||
"glDeleteFramebuffers",
|
||||
"glDeleteProgram",
|
||||
"glDeleteShader",
|
||||
"glDeleteTextures",
|
||||
"glDisable",
|
||||
"glDisableVertexAttribArray",
|
||||
"glDrawArrays",
|
||||
"glEnable",
|
||||
"glEnableVertexAttribArray",
|
||||
"glFinish",
|
||||
"glFramebufferTexture2D",
|
||||
"glGenFramebuffers",
|
||||
"glGenTextures",
|
||||
"glGetAttribLocation",
|
||||
"glGetError",
|
||||
"glGetIntegerv",
|
||||
"glGetProgramInfoLog",
|
||||
"glGetProgramiv",
|
||||
"glGetShaderInfoLog",
|
||||
"glGetShaderiv",
|
||||
"glGetString",
|
||||
"glGetUniformLocation",
|
||||
"glLinkProgram",
|
||||
"glReadPixels",
|
||||
"glScissor",
|
||||
"glShaderSource",
|
||||
"glTexImage2D",
|
||||
"glTexParameteri",
|
||||
"glTexSubImage2D",
|
||||
"glUniform1f",
|
||||
"glUniform1i",
|
||||
"glUniform2f",
|
||||
"glUniform4f",
|
||||
"glUniformMatrix4fv",
|
||||
"glUseProgram",
|
||||
"glVertexAttribPointer",
|
||||
"glViewport",
|
||||
]
|
||||
@@ -1,72 +0,0 @@
|
||||
#@PydevCodeAnalysisIgnore
|
||||
#cython: profile=False
|
||||
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from renpy.uguu.gl cimport *
|
||||
from renpy.gl.gldraw cimport *
|
||||
from renpy.gl.gldraw import Rtt
|
||||
|
||||
class CopyRtt(Rtt):
|
||||
"""
|
||||
This class uses texture copying to implement Render-to-texture.
|
||||
"""
|
||||
|
||||
def init(self):
|
||||
return
|
||||
|
||||
def deinit(self):
|
||||
return
|
||||
|
||||
def render(self, Environ environ, texture, x, y, w, h, draw_func):
|
||||
"""
|
||||
This function is called to trigger a rendering to a texture.
|
||||
`x`, `y`, `w`, and `h` specify the location and dimensions of
|
||||
the sub-image to render to the texture. `draw_func` is called
|
||||
to render the texture.
|
||||
"""
|
||||
|
||||
environ.viewport(0, 0, w, h)
|
||||
environ.ortho(x, x + w, y, y + h, -1, 1)
|
||||
|
||||
draw_func(x, y, w, h)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture)
|
||||
|
||||
glCopyTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
w,
|
||||
h)
|
||||
|
||||
def end(self):
|
||||
"""
|
||||
This is called when a Render-to-texture session ends.
|
||||
"""
|
||||
|
||||
def get_size_limit(self, dimension):
|
||||
return dimension
|
||||
@@ -1,115 +0,0 @@
|
||||
#@PydevCodeAnalysisIgnore
|
||||
#cython: profile=False
|
||||
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from renpy.uguu.gl cimport *
|
||||
from renpy.gl.gldraw cimport *
|
||||
from renpy.gl.gldraw import Rtt
|
||||
|
||||
import renpy
|
||||
|
||||
# The framebuffer object we use.
|
||||
cdef GLuint fbo
|
||||
|
||||
# The root framebuffer.
|
||||
cdef GLint root_fbo
|
||||
|
||||
# The renderbuffer object we use.
|
||||
cdef GLuint texture
|
||||
|
||||
class FboRtt(Rtt):
|
||||
"""
|
||||
This class uses texture copying to implement Render-to-texture.
|
||||
"""
|
||||
|
||||
def init(self):
|
||||
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &root_fbo);
|
||||
renpy.display.log.write("Root FBO is: %d", root_fbo)
|
||||
|
||||
glGenFramebuffers(1, &fbo)
|
||||
glGenTextures(1, &texture)
|
||||
|
||||
cdef int i
|
||||
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &i)
|
||||
self.size_limit = min(i, 2048)
|
||||
renpy.display.log.write("FBO Maximum Texture Size: %d", self.size_limit)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.size_limit, self.size_limit, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo)
|
||||
glFramebufferTexture2D(
|
||||
GL_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D,
|
||||
texture,
|
||||
0)
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, root_fbo)
|
||||
|
||||
|
||||
def deinit(self):
|
||||
"""
|
||||
Called before changing the GL context.
|
||||
"""
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, root_fbo)
|
||||
glDeleteFramebuffers(1, &fbo)
|
||||
glDeleteTextures(1, &texture)
|
||||
|
||||
def begin(self):
|
||||
"""
|
||||
This function should be called when a Render-to-texture
|
||||
session begins. It's responsible for setting the GPU to
|
||||
RTT mode.
|
||||
"""
|
||||
|
||||
def render(self, Environ environ, texture, x, y, w, h, draw_func):
|
||||
"""
|
||||
This function is called to trigger a rendering to a texture.
|
||||
`x`, `y`, `w`, and `h` specify the location and dimensions of
|
||||
the sub-image to render to the texture. `draw_func` is called
|
||||
to render the texture.
|
||||
"""
|
||||
|
||||
try:
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo)
|
||||
|
||||
environ.viewport(0, 0, w, h)
|
||||
environ.ortho(x, x + w, y, y + h, -1, 1)
|
||||
|
||||
draw_func(x, y, w, h)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)
|
||||
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, w, h, 0)
|
||||
|
||||
finally:
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, root_fbo)
|
||||
|
||||
|
||||
def get_size_limit(self, dimension):
|
||||
return self.size_limit
|
||||
@@ -1,104 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from renpy.display.matrix cimport Matrix
|
||||
cimport renpy.display.render as render
|
||||
from renpy.gl.gldraw cimport Environ
|
||||
|
||||
cdef class TextureCore:
|
||||
cdef public int width
|
||||
cdef public int height
|
||||
cdef public int generation
|
||||
cdef public unsigned int number
|
||||
cdef unsigned int format
|
||||
cdef double xmul
|
||||
cdef double xadd
|
||||
cdef double ymul
|
||||
cdef double yadd
|
||||
cdef object premult
|
||||
cdef tuple premult_size
|
||||
cdef int premult_left
|
||||
cdef int premult_right
|
||||
cdef int premult_top
|
||||
cdef int premult_bottom
|
||||
cdef bint nearest
|
||||
cdef public list free_list
|
||||
|
||||
cdef void make_ready(TextureCore)
|
||||
cdef void make_nearest(TextureCore)
|
||||
cdef void make_linear(TextureCore)
|
||||
cpdef int allocate(TextureCore)
|
||||
|
||||
cdef public object debug
|
||||
|
||||
cdef class TextureGrid:
|
||||
|
||||
|
||||
cdef object __weakref__
|
||||
|
||||
cdef public int width
|
||||
cdef public int height
|
||||
cdef list rows
|
||||
cdef list columns
|
||||
cdef list tiles # list of lists.
|
||||
cdef public TextureGrid half_cache
|
||||
|
||||
cpdef void make_ready(self, bint nearest)
|
||||
|
||||
cdef public object debug
|
||||
cdef public bint ready
|
||||
|
||||
|
||||
cpdef blit(
|
||||
TextureGrid tg,
|
||||
double sx,
|
||||
double sy,
|
||||
Matrix transform,
|
||||
double alpha,
|
||||
double over,
|
||||
Environ environ,
|
||||
bint nearest)
|
||||
|
||||
cpdef blend(
|
||||
TextureGrid tg0,
|
||||
TextureGrid tg1,
|
||||
double sx,
|
||||
double sy,
|
||||
Matrix transform,
|
||||
double alpha,
|
||||
double over,
|
||||
double fraction,
|
||||
Environ environ,
|
||||
bint nearest)
|
||||
|
||||
cpdef imageblend(
|
||||
TextureGrid tg0,
|
||||
TextureGrid tg1,
|
||||
TextureGrid tg2,
|
||||
double sx,
|
||||
double sy,
|
||||
Matrix transform,
|
||||
double alpha,
|
||||
double over,
|
||||
double fraction,
|
||||
int ramp,
|
||||
Environ environ,
|
||||
bint nearest)
|
||||
File diff suppressed because it is too large
Load Diff
+1
-5
@@ -63,8 +63,7 @@ def onetime_init():
|
||||
if os.path.exists(fn):
|
||||
dll = fn
|
||||
|
||||
if not PY2:
|
||||
dll = dll.encode("utf-8")
|
||||
dll = dll.encode("utf-8")
|
||||
|
||||
if not renpy.gl2.live2dmodel.load(dll): # type: ignore
|
||||
raise Exception("Could not load Live2D. {} was not found.".format(dll))
|
||||
@@ -88,9 +87,6 @@ def init():
|
||||
if live2dmodel is None:
|
||||
raise Exception("Live2D has not been built.")
|
||||
|
||||
if not renpy.config.gl2:
|
||||
raise Exception("Live2D requires that config.gl2 be True.")
|
||||
|
||||
if renpy.emscripten:
|
||||
raise Exception("Live2D is not supported the web platform.")
|
||||
|
||||
|
||||
+195
-144
@@ -23,114 +23,141 @@
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals # type: ignore
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
from typing import Any
|
||||
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import linecache
|
||||
import contextlib
|
||||
|
||||
import renpy
|
||||
|
||||
from renpy.lexersupport import match_logical_word
|
||||
|
||||
# The filename that's in the line text cache.
|
||||
line_text_filename = ""
|
||||
|
||||
# The content of the line text cache.
|
||||
line_text_cache = [ ]
|
||||
_python_updatecache = linecache.updatecache
|
||||
|
||||
|
||||
def get_line_text(filename, lineno):
|
||||
def _updatecache(filename: str, module_globals: dict[str, Any] | None = None) -> list[str]:
|
||||
"""
|
||||
Gets the text of a line, in a best-effort way, for debugging purposes. May
|
||||
return just a newline, if the line doesn't exist.
|
||||
Monkeypatch internal linecache.updatecache to work around RenPy python code
|
||||
being in different files even in the same namespace. Assume filename is one
|
||||
of '_ren.py', '.rpy' or '.rpym'.
|
||||
|
||||
This is needed because a lot of Python modules (namely traceback) assume
|
||||
that the compiled python code is always comes from the python file and uses
|
||||
linecache to get the source code, which tries to use PEP302 loader, which
|
||||
for RenPy python code points to renpy.minstore.
|
||||
"""
|
||||
|
||||
global line_text_filename
|
||||
global line_text_cache
|
||||
|
||||
import linecache
|
||||
full_filename = renpy.exports.unelide_filename(filename)
|
||||
|
||||
|
||||
if full_filename != line_text_filename:
|
||||
|
||||
line_text_filename = full_filename
|
||||
if filename.endswith(("_ren.py", ".rpy", ".rpym")):
|
||||
# Assume filename also relative to basedir or renpy_base.
|
||||
full_fn = renpy.lexer.unelide_filename(filename)
|
||||
# If we can't find absolute path that way, assume we are
|
||||
# in build, so we can't show source.
|
||||
if not (os.path.isabs(full_fn) and os.path.exists(full_fn)):
|
||||
linecache.cache.pop(filename, None)
|
||||
return []
|
||||
|
||||
try:
|
||||
|
||||
with open(full_filename, "rb") as f:
|
||||
with open(full_fn, "rb") as f:
|
||||
data = f.read().decode("utf-8", "python_strict")
|
||||
|
||||
if full_filename.endswith("_ren.py"):
|
||||
if full_fn.endswith("_ren.py"):
|
||||
data = ren_py_to_rpy(data, None)
|
||||
|
||||
data += "\n\n"
|
||||
|
||||
line_text_cache = data.split("\n")
|
||||
lines = data.split("\n")
|
||||
if lines and lines[0].startswith("\ufeff"):
|
||||
lines[0] = lines[0][1:]
|
||||
|
||||
lines = [line.removesuffix("\r") + "\n" for line in lines]
|
||||
|
||||
stat = os.stat(full_fn)
|
||||
linecache.cache[filename] = stat.st_size, stat.st_mtime, lines, full_fn
|
||||
return lines
|
||||
|
||||
except Exception:
|
||||
line_text_cache = [ ]
|
||||
return []
|
||||
|
||||
if lineno <= len(line_text_cache):
|
||||
return line_text_cache[lineno - 1] + "\n"
|
||||
else:
|
||||
return "\n"
|
||||
return _python_updatecache(filename, module_globals)
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
linecache.updatecache = _updatecache
|
||||
|
||||
def __init__(self, filename, number, msg, line=None, pos=None, first=False):
|
||||
message = u"File \"%s\", line %d: %s" % (unicode_filename(filename), number, msg)
|
||||
|
||||
if line:
|
||||
if isinstance(line, list):
|
||||
line = "".join(line)
|
||||
class ParseError(SyntaxError):
|
||||
"""
|
||||
Special exception type for syntax errors in Ren'Py.
|
||||
This exception includes syntax errors of Python code, converted to
|
||||
appropriate report style, and Ren'Py own syntax errors in user script.
|
||||
"""
|
||||
|
||||
lines = line.split('\n')
|
||||
_message: str | None = None
|
||||
|
||||
if len(lines) > 1:
|
||||
open_string = None
|
||||
i = 0
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
filename: str,
|
||||
lineno: int,
|
||||
offset: int | None = None,
|
||||
text: str | None = None,
|
||||
end_lineno: int | None = None,
|
||||
end_offset: int | None = None,
|
||||
):
|
||||
super().__init__(message, (
|
||||
unicode_filename(filename),
|
||||
lineno, offset,
|
||||
text,
|
||||
end_lineno, end_offset))
|
||||
|
||||
while i < len(lines[0]):
|
||||
c = lines[0][i]
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
Fully formatted message of the error close to the result of
|
||||
`traceback.print_exception_only`.
|
||||
"""
|
||||
if self._message is None:
|
||||
message = f'File "{self.filename}", line {self.lineno}: {self.msg}'
|
||||
if self.text is not None:
|
||||
# Neither Python nor this class does not support multiline syntax error code.
|
||||
# Just strip the first line of provided code.
|
||||
text = self.text.split("\n")[0]
|
||||
|
||||
if c == "\\":
|
||||
i += 1
|
||||
elif c == open_string:
|
||||
open_string = None
|
||||
elif open_string:
|
||||
pass
|
||||
elif c == '`' or c == '\'' or c == '"':
|
||||
open_string = c
|
||||
# Remove ending escape chars, so we can render it.
|
||||
text = text.rstrip()
|
||||
|
||||
i += 1
|
||||
# And also replace any escape chars at the start with an indent.
|
||||
message += f'\n {text.lstrip()}'
|
||||
|
||||
if open_string:
|
||||
message += "\n(Perhaps you left out a %s at the end of the first line.)" % open_string
|
||||
if self.offset is not None:
|
||||
offset = self.offset
|
||||
|
||||
for l in lines:
|
||||
message += "\n " + l
|
||||
|
||||
if pos is not None:
|
||||
if pos <= len(l):
|
||||
message += "\n " + " " * pos + "^"
|
||||
pos = None
|
||||
# Fallback to single caret for cases end_offset is before offset.
|
||||
if self.end_offset is None or self.end_offset <= offset:
|
||||
end_offset = offset + 1
|
||||
else:
|
||||
pos -= len(l)
|
||||
end_offset = self.end_offset
|
||||
|
||||
if first:
|
||||
break
|
||||
left_spaces = len(text) - len(text.lstrip())
|
||||
offset -= left_spaces
|
||||
end_offset -= left_spaces
|
||||
|
||||
self.message = message
|
||||
if offset >= 1:
|
||||
caret_space = ' ' * (offset - 1)
|
||||
carets = '^' * (end_offset - offset)
|
||||
message += f"\n {caret_space}{carets}"
|
||||
|
||||
Exception.__init__(self, message)
|
||||
for note in getattr(self, "__notes__", ()):
|
||||
message += f"\n{note}"
|
||||
|
||||
def __unicode__(self):
|
||||
return self.message
|
||||
self._message = message
|
||||
|
||||
return self._message
|
||||
|
||||
def defer(self, queue):
|
||||
renpy.parser.deferred_parse_errors[queue].append(self.message)
|
||||
@@ -356,7 +383,8 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
|
||||
c = data[pos]
|
||||
|
||||
if c == u'\t':
|
||||
raise ParseError(filename, number, "Tab characters are not allowed in Ren'Py scripts.")
|
||||
raise ParseError("Tab characters are not allowed in Ren'Py scripts.",
|
||||
filename, number)
|
||||
|
||||
if c == u'\n' and not parendepth:
|
||||
|
||||
@@ -496,14 +524,80 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
|
||||
pos = end
|
||||
|
||||
if (pos - startpos) > 65536:
|
||||
raise ParseError(filename, start_number, "Overly long logical line. (Check strings and parenthesis.)", line=line, first=True)
|
||||
err = ParseError(
|
||||
"Overly long logical line.",
|
||||
filename, start_number,
|
||||
text="".join(line))
|
||||
err.add_note("Check strings and parenthesis.")
|
||||
raise err
|
||||
|
||||
if line:
|
||||
raise ParseError(filename, start_number, "is not terminated with a newline. (Check strings and parenthesis.)", line=line, first=True)
|
||||
err = ParseError("is not terminated with a newline.",
|
||||
filename, start_number,
|
||||
text="".join(line))
|
||||
err.add_note("Check strings and parenthesis.")
|
||||
raise err
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def depth_split(l):
|
||||
"""
|
||||
Returns the length of the line's prefix, and the rest of the line.
|
||||
"""
|
||||
|
||||
depth = 0
|
||||
index = 0
|
||||
|
||||
while True:
|
||||
if l[index] == ' ':
|
||||
depth += 1
|
||||
index += 1
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
return depth, l[index:]
|
||||
|
||||
# i, min_depth -> block, new_i
|
||||
def gll_core(lines, i, min_depth):
|
||||
"""
|
||||
Recursively groups lines into blocks.
|
||||
|
||||
Given the line
|
||||
"""
|
||||
|
||||
rv = []
|
||||
depth = None
|
||||
|
||||
while i < len(lines):
|
||||
|
||||
filename, number, text = lines[i]
|
||||
|
||||
line_depth, rest = depth_split(text)
|
||||
|
||||
# This catches a block exit.
|
||||
if line_depth < min_depth:
|
||||
break
|
||||
|
||||
if depth is None:
|
||||
depth = line_depth
|
||||
|
||||
if depth != line_depth:
|
||||
raise ParseError(
|
||||
"Indentation mismatch.",
|
||||
filename, number, text=text)
|
||||
|
||||
# Advance to the next line.
|
||||
i += 1
|
||||
|
||||
# Try parsing a block associated with this line.
|
||||
block, i = gll_core(lines, i, depth + 1)
|
||||
|
||||
rv.append((filename, number, rest, block))
|
||||
|
||||
return rv, i
|
||||
|
||||
def group_logical_lines(lines):
|
||||
"""
|
||||
This takes as input the list of logical line triples output from
|
||||
@@ -513,67 +607,16 @@ def group_logical_lines(lines):
|
||||
no block is associated with this line.)
|
||||
"""
|
||||
|
||||
# Returns the depth of a line, and the rest of the line.
|
||||
def depth_split(l):
|
||||
|
||||
depth = 0
|
||||
index = 0
|
||||
|
||||
while True:
|
||||
if l[index] == ' ':
|
||||
depth += 1
|
||||
index += 1
|
||||
continue
|
||||
|
||||
# if l[index] == '\t':
|
||||
# index += 1
|
||||
# depth = depth + 8 - (depth % 8)
|
||||
# continue
|
||||
|
||||
break
|
||||
|
||||
return depth, l[index:]
|
||||
|
||||
# i, min_depth -> block, new_i
|
||||
def gll_core(i, min_depth):
|
||||
|
||||
rv = []
|
||||
depth = None
|
||||
|
||||
while i < len(lines):
|
||||
|
||||
filename, number, text = lines[i]
|
||||
|
||||
line_depth, rest = depth_split(text)
|
||||
|
||||
# This catches a block exit.
|
||||
if line_depth < min_depth:
|
||||
break
|
||||
|
||||
if depth is None:
|
||||
depth = line_depth
|
||||
|
||||
if depth != line_depth:
|
||||
raise ParseError(filename, number, "Indentation mismatch.")
|
||||
|
||||
# Advance to the next line.
|
||||
i += 1
|
||||
|
||||
# Try parsing a block associated with this line.
|
||||
block, i = gll_core(i, depth + 1)
|
||||
|
||||
rv.append((filename, number, rest, block))
|
||||
|
||||
return rv, i
|
||||
|
||||
if lines:
|
||||
|
||||
filename, number, text = lines[0]
|
||||
|
||||
if depth_split(text)[0] != 0:
|
||||
raise ParseError(filename, number, "Unexpected indentation at start of file.")
|
||||
raise ParseError(
|
||||
"Unexpected indentation at start of file.",
|
||||
filename, number, text=text)
|
||||
|
||||
return gll_core(0, 0)[0]
|
||||
return gll_core(lines, 0, 0)[0]
|
||||
|
||||
|
||||
# A list of keywords which should not be parsed as names, because
|
||||
@@ -820,7 +863,12 @@ class Lexer(object):
|
||||
if (self.line == -1) and self.block:
|
||||
self.filename, self.number, self.text, self.subblock = self.block[0]
|
||||
|
||||
raise ParseError(self.filename, self.number, msg, self.text, self.pos)
|
||||
raise ParseError(
|
||||
msg,
|
||||
self.filename,
|
||||
self.number,
|
||||
self.pos,
|
||||
self.text)
|
||||
|
||||
def deferred_error(self, queue, msg):
|
||||
"""
|
||||
@@ -834,7 +882,10 @@ class Lexer(object):
|
||||
if (self.line == -1) and self.block:
|
||||
self.filename, self.number, self.text, self.subblock = self.block[0]
|
||||
|
||||
ParseError(self.filename, self.number, msg, self.text, self.pos).defer(queue)
|
||||
ParseError(
|
||||
msg, self.filename,
|
||||
self.number, self.pos + 1,
|
||||
self.text).defer(queue)
|
||||
|
||||
def eol(self):
|
||||
"""
|
||||
@@ -1486,6 +1537,20 @@ class Lexer(object):
|
||||
self.pos = len(self.text)
|
||||
return self.text[pos:].strip()
|
||||
|
||||
def _process_python_block(self, block, indent, rv, line_holder):
|
||||
for _fn, ln, text, subblock in block:
|
||||
|
||||
while line_holder.line < ln:
|
||||
rv.append(indent + '\n')
|
||||
line_holder.line += 1
|
||||
|
||||
linetext = indent + text + '\n'
|
||||
|
||||
rv.append(linetext)
|
||||
line_holder.line += linetext.count('\n')
|
||||
|
||||
self._process_python_block(subblock, indent + ' ', rv, line_holder)
|
||||
|
||||
def python_block(self):
|
||||
"""
|
||||
Returns the subblock of this code, and subblocks of that
|
||||
@@ -1495,25 +1560,10 @@ class Lexer(object):
|
||||
|
||||
rv = [ ]
|
||||
|
||||
o = LineNumberHolder()
|
||||
o.line = self.number
|
||||
line_holder = LineNumberHolder()
|
||||
line_holder.line = self.number
|
||||
|
||||
def process(block, indent):
|
||||
|
||||
for _fn, ln, text, subblock in block:
|
||||
|
||||
while o.line < ln:
|
||||
rv.append(indent + '\n')
|
||||
o.line += 1
|
||||
|
||||
linetext = indent + text + '\n'
|
||||
|
||||
rv.append(linetext)
|
||||
o.line += linetext.count('\n')
|
||||
|
||||
process(subblock, indent + ' ')
|
||||
|
||||
process(self.subblock, '')
|
||||
self._process_python_block(self.subblock, '', rv, line_holder)
|
||||
return ''.join(rv)
|
||||
|
||||
def arguments(self):
|
||||
@@ -1581,7 +1631,7 @@ class Lexer(object):
|
||||
return sp
|
||||
|
||||
|
||||
def ren_py_to_rpy(text, filename):
|
||||
def ren_py_to_rpy(text: str, filename: str | None) -> str:
|
||||
"""
|
||||
Transforms an _ren.py file into the equivalent .rpy file. This should retain line numbers.
|
||||
|
||||
@@ -1659,8 +1709,9 @@ def ren_py_to_rpy(text, filename):
|
||||
raise Exception('In {!r}, there are no """renpy blocks, so every line is ignored.'.format(filename))
|
||||
|
||||
if state == RENPY:
|
||||
raise Exception('In {!r}, there is a """renpy block at line {} that is not terminated by """.'.format(filename,
|
||||
open_linenumber))
|
||||
raise Exception(
|
||||
'In {!r}, there is a """renpy block at line {} that is not terminated by """.'.format(
|
||||
filename, open_linenumber))
|
||||
|
||||
rv = "\n".join(result)
|
||||
|
||||
|
||||
+7
-3
@@ -36,6 +36,8 @@ import io
|
||||
import unicodedata
|
||||
import time
|
||||
|
||||
from importlib.util import spec_from_loader
|
||||
|
||||
from pygame_sdl2.rwobject import RWopsIO
|
||||
|
||||
from renpy.compat.pickle import loads
|
||||
@@ -833,14 +835,16 @@ class RenpyImporter(object):
|
||||
|
||||
return None
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
if path is not None:
|
||||
for i in path:
|
||||
if self.translate(fullname, i):
|
||||
return RenpyImporter(i)
|
||||
return spec_from_loader(name=fullname, loader=RenpyImporter(i), origin=path)
|
||||
|
||||
if self.translate(fullname):
|
||||
return self
|
||||
return spec_from_loader(name=fullname, loader=self, origin=path)
|
||||
|
||||
|
||||
def load_module(self, fullname, mode="full"):
|
||||
"""
|
||||
|
||||
+2
-8
@@ -82,16 +82,10 @@ def save_dump(roots, log):
|
||||
|
||||
elif isinstance(o, types.MethodType):
|
||||
|
||||
if PY2:
|
||||
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__func__.__name__) # type: ignore
|
||||
else:
|
||||
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__name__)
|
||||
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__name__)
|
||||
|
||||
elif isinstance(o, types.FunctionType):
|
||||
if PY2:
|
||||
name = o.__name__
|
||||
else:
|
||||
name = o.__qualname__ or o.__name__
|
||||
name = o.__qualname__ or o.__name__
|
||||
|
||||
o_repr = o.__module__ + '.' + name
|
||||
|
||||
|
||||
+1
-4
@@ -121,10 +121,7 @@ def cycle_finder(o, name):
|
||||
o_repr = "<" + o.__class__.__name__ + ">"
|
||||
|
||||
elif isinstance(o, types.MethodType):
|
||||
if PY2:
|
||||
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__func__.__name__) # type: ignore
|
||||
else:
|
||||
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__name__)
|
||||
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__name__)
|
||||
|
||||
elif isinstance(o, object):
|
||||
o_repr = "<{0}>".format(type(o).__name__)
|
||||
|
||||
@@ -201,6 +201,3 @@ __all__ = [
|
||||
'ui',
|
||||
'unicode',
|
||||
]
|
||||
|
||||
if PY2:
|
||||
__all__ = [ bytes(i) for i in __all__ ] # type: ignore
|
||||
|
||||
+2
-6
@@ -26,12 +26,8 @@ from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, r
|
||||
|
||||
|
||||
# Allow pickling NoneType.
|
||||
if PY2:
|
||||
import __builtin__ # type: ignore
|
||||
__builtin__.NoneType = type(None)
|
||||
else:
|
||||
import builtins
|
||||
builtins.NoneType = type(None) # type: ignore
|
||||
import builtins
|
||||
builtins.NoneType = type(None) # type: ignore
|
||||
|
||||
|
||||
class Object(object):
|
||||
|
||||
+15
-22
@@ -24,7 +24,6 @@ from __future__ import division, absolute_import, with_statement, print_function
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
from itertools import chain as _chain
|
||||
import collections
|
||||
|
||||
import renpy
|
||||
|
||||
@@ -43,8 +42,7 @@ class Parameter(object):
|
||||
|
||||
empty = None
|
||||
|
||||
def __init__(self, name, kind, default=empty):
|
||||
# default should only be passed by keyword, todo when PY3-only
|
||||
def __init__(self, name, kind, *, default=empty):
|
||||
self.name = name
|
||||
self.kind = kind
|
||||
self.default = default
|
||||
@@ -90,10 +88,9 @@ class ValuedParameter(Parameter):
|
||||
|
||||
class empty: pass # singleton, should be picklable
|
||||
|
||||
def __init__(self, name, kind, default=empty):
|
||||
# default should only be passed by keyword, todo when PY3-only
|
||||
def __init__(self, name, kind, *, default=empty):
|
||||
# this method is redefined in order to change default's default value
|
||||
super(ValuedParameter, self).__init__(name, kind, default)
|
||||
super(ValuedParameter, self).__init__(name, kind, default=default)
|
||||
|
||||
def default_value(self, *args, **kwargs):
|
||||
return self.default
|
||||
@@ -123,8 +120,7 @@ class Signature(object):
|
||||
if parameters is None:
|
||||
self.parameters = {}
|
||||
else:
|
||||
# when in PY3-only, turn this into MappingProxyType o dict
|
||||
self.parameters = collections.OrderedDict((param.name, param) for param in parameters)
|
||||
self.parameters = {param.name: param for param in parameters}
|
||||
|
||||
@staticmethod
|
||||
def legacy_params(parameters, positional, extrapos, extrakw, last_posonly=None, first_kwonly=None):
|
||||
@@ -241,8 +237,6 @@ class Signature(object):
|
||||
- applies the defaults automatically (and lazily, as per the above)
|
||||
"""
|
||||
|
||||
# when in PY3-only, uncomment the "from None" in the raise statements
|
||||
|
||||
if not renpy.config.developer:
|
||||
ignore_errors = True
|
||||
|
||||
@@ -250,7 +244,7 @@ class Signature(object):
|
||||
|
||||
def _raise(exct, msg, *argz, **kwargz):
|
||||
if not ignore_errors:
|
||||
raise exct(msg.format(*argz, **kwargz)) # from None
|
||||
raise exct(msg.format(*argz, **kwargz)) from None
|
||||
|
||||
# code mostly taken from stdlib's inspect.Signature._bind
|
||||
arguments = {}
|
||||
@@ -304,7 +298,7 @@ class Signature(object):
|
||||
argtype = ''
|
||||
msg = 'missing a required{argtype} argument: {arg!r}'
|
||||
msg = msg.format(arg=param.name, argtype=argtype)
|
||||
raise TypeError(msg) # from None
|
||||
raise TypeError(msg) from None
|
||||
else:
|
||||
# We have a positional argument to process
|
||||
try:
|
||||
@@ -362,7 +356,7 @@ class Signature(object):
|
||||
if (not (partial or ignore_errors) and param.kind != param.VAR_POSITIONAL and
|
||||
param.default is param.empty):
|
||||
raise TypeError('missing a required argument: {arg!r}'. \
|
||||
format(arg=param_name)) # from None
|
||||
format(arg=param_name)) from None
|
||||
|
||||
else:
|
||||
if param.kind == param.POSITIONAL_ONLY:
|
||||
@@ -458,8 +452,8 @@ def apply_arguments(parameters, args, kwargs, ignore_errors=False):
|
||||
class ArgumentInfo(renpy.object.Object):
|
||||
|
||||
__version__ = 1
|
||||
starred_indexes = set()
|
||||
doublestarred_indexes = set()
|
||||
starred_indexes = frozenset()
|
||||
doublestarred_indexes = frozenset()
|
||||
|
||||
def after_upgrade(self, version):
|
||||
if version < 1:
|
||||
@@ -468,16 +462,13 @@ class ArgumentInfo(renpy.object.Object):
|
||||
extrakw = self.extrakw # type: ignore
|
||||
length = len(arguments) + bool(extrapos) + bool(extrakw)
|
||||
if extrapos:
|
||||
self.starred_indexes = { length - 1 }
|
||||
self.starred_indexes = { length - 1 - bool(extrakw) }
|
||||
arguments.append((None, extrapos))
|
||||
|
||||
if extrakw:
|
||||
self.doublestarred_indexes = { length - 1 }
|
||||
arguments.append((None, extrakw))
|
||||
|
||||
if extrapos and extrakw:
|
||||
self.starred_indexes = { length - 2 }
|
||||
|
||||
def __init__(self, arguments, starred_indexes=None, doublestarred_indexes=None):
|
||||
|
||||
# A list of (keyword, expression) pairs.
|
||||
@@ -485,10 +476,12 @@ class ArgumentInfo(renpy.object.Object):
|
||||
self.arguments = arguments
|
||||
|
||||
# Indexes of arguments to be considered as * unpacking
|
||||
self.starred_indexes = starred_indexes or set()
|
||||
if starred_indexes is not None:
|
||||
self.starred_indexes = starred_indexes
|
||||
|
||||
# Indexes of arguments to be considered as ** unpacking.
|
||||
self.doublestarred_indexes = doublestarred_indexes or set()
|
||||
if doublestarred_indexes is not None:
|
||||
self.doublestarred_indexes = doublestarred_indexes
|
||||
|
||||
def evaluate(self, scope=None):
|
||||
"""
|
||||
@@ -541,4 +534,4 @@ class ArgumentInfo(renpy.object.Object):
|
||||
|
||||
|
||||
EMPTY_PARAMETERS = Signature()
|
||||
EMPTY_ARGUMENTS = ArgumentInfo([ ], None, None)
|
||||
EMPTY_ARGUMENTS = ArgumentInfo((), None, None)
|
||||
|
||||
+10
-4
@@ -31,7 +31,7 @@ import time
|
||||
import renpy
|
||||
import renpy.ast as ast
|
||||
|
||||
from renpy.parameter import Parameter
|
||||
from renpy.parameter import EMPTY_ARGUMENTS, Parameter
|
||||
|
||||
from renpy.lexer import (
|
||||
list_logical_lines,
|
||||
@@ -43,7 +43,6 @@ from renpy.lexer import (
|
||||
munge_filename,
|
||||
elide_filename,
|
||||
unelide_filename,
|
||||
get_line_text,
|
||||
SubParse,
|
||||
)
|
||||
|
||||
@@ -475,6 +474,9 @@ def parse_arguments(l):
|
||||
if not l.match(r'\('):
|
||||
return None
|
||||
|
||||
if l.match(r'\)'):
|
||||
return EMPTY_ARGUMENTS
|
||||
|
||||
arguments = [ ]
|
||||
starred_indexes = set()
|
||||
doublestarred_indexes = set()
|
||||
@@ -1766,8 +1768,10 @@ def report_parse_errors():
|
||||
full_text = ""
|
||||
|
||||
f, error_fn = renpy.error.open_error_file("errors.txt", "w")
|
||||
|
||||
screen_parse_errors = []
|
||||
with f:
|
||||
f.write("\ufeff") # BOM
|
||||
f.write("\ufeff") # BOM
|
||||
|
||||
print("I'm sorry, but errors were detected in your script. Please correct the", file=f)
|
||||
print("errors listed below, and try again.", file=f)
|
||||
@@ -1784,6 +1788,8 @@ def report_parse_errors():
|
||||
print("", file=f)
|
||||
print(i, file=f)
|
||||
|
||||
screen_parse_errors.append(i)
|
||||
|
||||
try:
|
||||
print("")
|
||||
print(i)
|
||||
@@ -1794,7 +1800,7 @@ def report_parse_errors():
|
||||
print("Ren'Py Version:", renpy.version, file=f)
|
||||
print(str(time.ctime()), file=f)
|
||||
|
||||
renpy.display.error.report_parse_errors(full_text, error_fn)
|
||||
renpy.display.error.report_parse_errors(screen_parse_errors, error_fn)
|
||||
|
||||
try:
|
||||
if renpy.game.args.command == "run" or renpy.game.args.errors_in_editor: # type: ignore
|
||||
|
||||
+1
-1
@@ -233,7 +233,7 @@ def init():
|
||||
disk, so that we can configure the savelocation system.
|
||||
"""
|
||||
|
||||
if renpy.config.early_developer and not PY2:
|
||||
if renpy.config.early_developer:
|
||||
init_debug_pickler()
|
||||
|
||||
filename = os.path.join(renpy.config.savedir, "persistent.new") # type: ignore
|
||||
|
||||
@@ -1,789 +0,0 @@
|
||||
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
|
||||
import renpy # @UnusedImport
|
||||
from renpy.python import py_compile
|
||||
|
||||
# Import the Python AST module, instead of the Ren'Py ast module.
|
||||
import ast
|
||||
|
||||
import zlib
|
||||
|
||||
from renpy.compat.pickle import loads, dumps
|
||||
|
||||
# The set of names that should be treated as constants.
|
||||
always_constants = { 'True', 'False', 'None' }
|
||||
|
||||
# The set of names that should be treated as pure functions.
|
||||
pure_functions = {
|
||||
# Python 2 builtins.
|
||||
"abs", "all", "any", "apply", "bin", "bool", "bytes", "callable", "chr",
|
||||
"cmp", "dict", "divmod",
|
||||
"filter", "float", "frozenset",
|
||||
"getattr", "globals", "hasattr", "hash", "hex", "int", "isinstance",
|
||||
"len", "list", "long", "map", "max", "min", "oct", "ord", "pow",
|
||||
"range", "reduce", "repr", "round", "set", "sorted",
|
||||
"str", "sum", "tuple", "unichr", "unicode", "vars", "zip",
|
||||
|
||||
# minstore.py
|
||||
"_",
|
||||
"_p",
|
||||
"absolute",
|
||||
"position",
|
||||
"__renpy__list__",
|
||||
"__renpy__dict__",
|
||||
"__renpy__set__",
|
||||
|
||||
# defaultstore.py
|
||||
"ImageReference", "Image", "Frame", "Solid", "LiveComposite", "LiveCrop",
|
||||
"LiveTile", "Flatten", "Null", "Window", "Viewport", "DynamicDisplayable",
|
||||
"ConditionSwitch", "ShowingSwitch", "Transform", "Animation", "Movie",
|
||||
"Particles", "SnowBlossom", "Text", "ParameterizedText", "FontGroup",
|
||||
"Drag", "Alpha", "AlphaMask", "Position", "Pan", "Move", "Motion", "Revolve", "Zoom",
|
||||
"RotoZoom", "FactorZoom", "SizeZoom", "Fade", "Dissolve", "ImageDissolve",
|
||||
"AlphaDissolve", "CropMove", "PushMove", "Pixellate", "OldMoveTransition",
|
||||
"MoveTransition", "MoveFactory", "MoveIn", "MoveOut", "ZoomInOut",
|
||||
"RevolveInOut", "MultipleTransition", "ComposeTransition", "Pause",
|
||||
"SubTransition", "ADVSpeaker", "ADVCharacter", "Speaker", "Character",
|
||||
"DynamicCharacter", "Fixed", "HBox", "VBox", "Grid", "AlphaBlend", "At",
|
||||
"color", "Color",
|
||||
|
||||
# ui.py
|
||||
|
||||
"ui.returns",
|
||||
"ui.jumps",
|
||||
"ui.jumpsoutofcontext",
|
||||
"ui.callsinnewcontext",
|
||||
"ui.invokesinnewcontext",
|
||||
"ui.gamemenus",
|
||||
|
||||
# renpy.py
|
||||
|
||||
"renpy.version_string",
|
||||
"renpy.version_only",
|
||||
"renpy.version_tuple",
|
||||
"renpy.version_name",
|
||||
"renpy.license",
|
||||
}
|
||||
|
||||
constants = { "config", "style" } | always_constants | pure_functions
|
||||
|
||||
# A set of names that should not be treated as global constants.
|
||||
not_constants = set()
|
||||
|
||||
# The base set for the local constants.
|
||||
local_constants = set()
|
||||
|
||||
|
||||
def const(name):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a variable in the store to be constant.
|
||||
|
||||
A variable is constant if nothing can change its value, or any value
|
||||
reached by indexing it or accessing its attributes. Variables must
|
||||
remain constant out of define, init, and translate python blocks.
|
||||
|
||||
`name`
|
||||
A string giving the name of the variable to declare constant.
|
||||
"""
|
||||
|
||||
if name not in not_constants:
|
||||
constants.add(name)
|
||||
|
||||
|
||||
def not_const(name):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a name in the store to be not constant.
|
||||
|
||||
This undoes the effect of calls to :func:`renpy.const` and
|
||||
:func:`renpy.pure`.
|
||||
|
||||
`name`
|
||||
The name to declare not constant.
|
||||
"""
|
||||
|
||||
constants.discard(name)
|
||||
pure_functions.discard(name)
|
||||
not_constants.add(name)
|
||||
|
||||
|
||||
def pure(fn):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a function as pure. A pure function must always return the
|
||||
same value when it is called with the same arguments, outside of
|
||||
define, init, and translate python blocks.
|
||||
|
||||
`fn`
|
||||
The name of the function to declare pure. This may either be a string
|
||||
containing the name of the function, or the function itself.
|
||||
If a string is passed and the function is inside the module,
|
||||
this string should contain the module name with the dot.
|
||||
|
||||
Returns `fn`, allowing this function to be used as a decorator.
|
||||
"""
|
||||
|
||||
name = fn
|
||||
|
||||
if not isinstance(name, basestring):
|
||||
name = fn.__name__
|
||||
|
||||
module = fn.__module__
|
||||
name = module + "." + name
|
||||
|
||||
if name.startswith("store."):
|
||||
name = name[6:]
|
||||
|
||||
if name not in not_constants:
|
||||
pure_functions.add(name)
|
||||
constants.add(name)
|
||||
|
||||
return fn
|
||||
|
||||
|
||||
class Control(object):
|
||||
"""
|
||||
Represents control flow.
|
||||
|
||||
`const`
|
||||
True if this statement always executes.
|
||||
|
||||
`loop`
|
||||
True if this corresponds to a loop.
|
||||
|
||||
`imagemap`
|
||||
True if this control is in a non-constant imagemap.
|
||||
"""
|
||||
|
||||
def __init__(self, const, loop, imagemap):
|
||||
self.const = const
|
||||
self.loop = loop
|
||||
self.imagemap = imagemap
|
||||
|
||||
|
||||
# Three levels of constness.
|
||||
GLOBAL_CONST = 2 # Expressions that are const everywhere.
|
||||
LOCAL_CONST = 1 # Expressions that are const with regard to a screen + parameters.
|
||||
NOT_CONST = 0 # Expressions that are not const.
|
||||
|
||||
|
||||
class DeltaSet(object):
|
||||
|
||||
def __init__(self, base, copy=None):
|
||||
"""
|
||||
Represents a set that stores its contents as differences from a base
|
||||
set.
|
||||
"""
|
||||
|
||||
self.base = base
|
||||
|
||||
if copy is not None:
|
||||
self.added = set(copy.added)
|
||||
self.removed = set(copy.removed)
|
||||
else:
|
||||
self.added = set()
|
||||
self.removed = set()
|
||||
|
||||
self.changed = False
|
||||
|
||||
def add(self, v):
|
||||
|
||||
if v in self.removed:
|
||||
self.removed.discard(v)
|
||||
self.changed = True
|
||||
elif v not in self.base and v not in self.added:
|
||||
self.added.add(v)
|
||||
self.changed = True
|
||||
|
||||
def discard(self, v):
|
||||
|
||||
if v in self.added:
|
||||
self.added.discard(v)
|
||||
self.changed = True
|
||||
elif v in self.base and v not in self.removed:
|
||||
self.removed.add(v)
|
||||
self.changed = True
|
||||
|
||||
def __contains__(self, v):
|
||||
return (v in self.added) or ((v in self.base) and (v not in self.removed))
|
||||
|
||||
def copy(self):
|
||||
return DeltaSet(self.base, self)
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
for i in self.base:
|
||||
if i not in self.removed:
|
||||
yield i
|
||||
|
||||
for i in self.added:
|
||||
yield i
|
||||
|
||||
|
||||
class Analysis(object):
|
||||
"""
|
||||
Represents the result of code analysis, and provides tools to perform
|
||||
code analysis.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
||||
# The parent context transcludes run in, or None if there is no parent
|
||||
# context.
|
||||
self.parent = parent
|
||||
|
||||
# Analyses of children, such a screens we use.
|
||||
self.children = { }
|
||||
|
||||
# The variables we consider to be not-constant.
|
||||
self.not_constant = DeltaSet(not_constants)
|
||||
|
||||
# Variables we consider to be locally constant.
|
||||
self.local_constant = DeltaSet(local_constants)
|
||||
|
||||
# Variables we consider to be globally constant.
|
||||
self.global_constant = DeltaSet(always_constants)
|
||||
|
||||
# The functions we consider to be pure.
|
||||
self.pure_functions = DeltaSet(pure_functions)
|
||||
|
||||
# Represents what we know about the current control.
|
||||
self.control = Control(True, False, False)
|
||||
|
||||
# The stack of const_flow values.
|
||||
self.control_stack = [ self.control ]
|
||||
|
||||
def get_child(self, identifier):
|
||||
if identifier in self.children:
|
||||
return self.children[identifier]
|
||||
|
||||
rv = Analysis(self)
|
||||
self.children[identifier] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def push_control(self, const=True, loop=False, imagemap=False):
|
||||
self.control = Control(self.control.const and const, loop, self.control.imagemap or imagemap)
|
||||
self.control_stack.append(self.control) # type: ignore
|
||||
|
||||
def pop_control(self):
|
||||
rv = self.control_stack.pop()
|
||||
self.control = self.control_stack[-1]
|
||||
return rv
|
||||
|
||||
def imagemap(self):
|
||||
"""
|
||||
Returns NOT_CONST if we're in a non-constant imagemap.
|
||||
"""
|
||||
|
||||
if self.control.imagemap:
|
||||
return NOT_CONST
|
||||
else:
|
||||
return GLOBAL_CONST
|
||||
|
||||
def exit_loop(self):
|
||||
"""
|
||||
Call this to indicate the current loop is being exited by the
|
||||
continue or break statements.
|
||||
"""
|
||||
|
||||
l = list(self.control_stack)
|
||||
l.reverse()
|
||||
|
||||
for i in l:
|
||||
i.const = False
|
||||
|
||||
if i.loop:
|
||||
break
|
||||
|
||||
def at_fixed_point(self):
|
||||
"""
|
||||
Returns True if we've reached a fixed point, where the analysis has
|
||||
not changed since the last time we called this function.
|
||||
"""
|
||||
|
||||
for i in self.children.values():
|
||||
if not i.at_fixed_point():
|
||||
return False
|
||||
|
||||
if (self.not_constant.changed or
|
||||
self.global_constant.changed or
|
||||
self.local_constant.changed or
|
||||
self.pure_functions.changed):
|
||||
|
||||
self.not_constant.changed = False
|
||||
self.global_constant.changed = False
|
||||
self.local_constant.changed = False
|
||||
self.pure_functions.changed = False
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def mark_constant(self, name):
|
||||
"""
|
||||
Marks `name` as a potential local constant.
|
||||
"""
|
||||
|
||||
if not name in self.not_constant:
|
||||
self.local_constant.add(name)
|
||||
self.global_constant.discard(name)
|
||||
self.pure_functions.discard(name)
|
||||
|
||||
def mark_not_constant(self, name):
|
||||
"""
|
||||
Marks `name` as definitely not-constant.
|
||||
"""
|
||||
|
||||
self.not_constant.add(name)
|
||||
|
||||
self.pure_functions.discard(name)
|
||||
self.local_constant.discard(name)
|
||||
self.global_constant.discard(name)
|
||||
|
||||
def is_constant(self, node):
|
||||
"""
|
||||
Returns true if `node` is constant for the purpose of screen
|
||||
language. Node should be a python AST node.
|
||||
|
||||
Screen language ignores object identity for the purposes of
|
||||
object equality.
|
||||
"""
|
||||
|
||||
def check_slice(slice): # @ReservedAssignment
|
||||
|
||||
if isinstance(slice, ast.Index):
|
||||
return check_node(slice.value) # type: ignore
|
||||
|
||||
elif isinstance(slice, ast.Slice):
|
||||
consts = [ ]
|
||||
|
||||
if slice.lower:
|
||||
consts.append(check_node(slice.lower))
|
||||
if slice.upper:
|
||||
consts.append(check_node(slice.upper))
|
||||
if slice.step:
|
||||
consts.append(check_node(slice.step))
|
||||
|
||||
if not consts:
|
||||
return GLOBAL_CONST
|
||||
else:
|
||||
return min(consts)
|
||||
|
||||
return NOT_CONST
|
||||
|
||||
def check_name(node):
|
||||
"""
|
||||
Check nodes that make up a name. This returns a pair:
|
||||
|
||||
* The first element is True if the node is constant, and False
|
||||
otherwise.
|
||||
* The second element is None if the node is constant or the name is
|
||||
not known, and the name otherwise.
|
||||
"""
|
||||
|
||||
if isinstance(node, ast.Name):
|
||||
const = NOT_CONST
|
||||
name = node.id
|
||||
|
||||
elif isinstance(node, ast.Attribute):
|
||||
const, name = check_name(node.value)
|
||||
|
||||
if name is not None:
|
||||
name = name + "." + node.attr
|
||||
|
||||
else:
|
||||
return check_node(node), None
|
||||
|
||||
if name in self.not_constant:
|
||||
return NOT_CONST, name
|
||||
elif name in self.global_constant:
|
||||
return GLOBAL_CONST, name
|
||||
elif name in self.local_constant:
|
||||
return LOCAL_CONST, name
|
||||
else:
|
||||
return const, name
|
||||
|
||||
def check_nodes(nodes):
|
||||
"""
|
||||
Checks a list of nodes for constness.
|
||||
"""
|
||||
|
||||
nodes = list(nodes)
|
||||
|
||||
if not nodes:
|
||||
return GLOBAL_CONST
|
||||
|
||||
return min(check_node(i) for i in nodes)
|
||||
|
||||
def check_node(node):
|
||||
"""
|
||||
Returns true if the ast node `node` is constant.
|
||||
"""
|
||||
|
||||
# This handles children that do not exist.
|
||||
if node is None:
|
||||
return GLOBAL_CONST
|
||||
|
||||
# PY3: see if there are new node types.
|
||||
|
||||
if isinstance(node, (ast.Num, ast.Str)):
|
||||
return GLOBAL_CONST
|
||||
|
||||
elif isinstance(node, (ast.List, ast.Tuple)):
|
||||
return check_nodes(node.elts)
|
||||
|
||||
elif isinstance(node, (ast.Attribute, ast.Name)):
|
||||
return check_name(node)[0]
|
||||
|
||||
elif isinstance(node, ast.BoolOp):
|
||||
return check_nodes(node.values)
|
||||
|
||||
elif isinstance(node, ast.BinOp):
|
||||
return min(
|
||||
check_node(node.left),
|
||||
check_node(node.right),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
return check_node(node.operand)
|
||||
|
||||
elif isinstance(node, ast.Call):
|
||||
const, name = check_name(node.func)
|
||||
|
||||
# The function must have a name, and must be declared pure.
|
||||
if (const != GLOBAL_CONST) or (name not in self.pure_functions):
|
||||
return NOT_CONST
|
||||
|
||||
consts = [ ]
|
||||
|
||||
# Arguments and keyword arguments must be pure.
|
||||
consts.append(check_nodes(node.args))
|
||||
consts.append(check_nodes(i.value for i in node.keywords))
|
||||
|
||||
if node.starargs is not None: # type: ignore
|
||||
consts.append(check_node(node.starargs)) # type: ignore
|
||||
|
||||
if node.kwargs is not None: # type: ignore
|
||||
consts.append(check_node(node.kwargs)) # type: ignore
|
||||
|
||||
return min(consts)
|
||||
|
||||
elif isinstance(node, ast.IfExp):
|
||||
return min(
|
||||
check_node(node.test),
|
||||
check_node(node.body),
|
||||
check_node(node.orelse),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Dict):
|
||||
return min(
|
||||
check_nodes(node.keys),
|
||||
check_nodes(node.values)
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Set):
|
||||
return check_nodes(node.elts)
|
||||
|
||||
elif isinstance(node, ast.Compare):
|
||||
return min(
|
||||
check_node(node.left),
|
||||
check_nodes(node.comparators),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Repr): # type: ignore
|
||||
return check_node(node.value)
|
||||
|
||||
elif isinstance(node, ast.Subscript):
|
||||
return min(
|
||||
check_node(node.value),
|
||||
check_slice(node.slice),
|
||||
)
|
||||
|
||||
return NOT_CONST
|
||||
|
||||
return check_node(node)
|
||||
|
||||
def is_constant_expr(self, expr):
|
||||
"""
|
||||
Compiles `expr` into an AST node, then returns the result of
|
||||
self.is_constant called on that node.
|
||||
"""
|
||||
|
||||
node, literal = ccache.ast_eval_literal(expr)
|
||||
|
||||
if literal:
|
||||
return GLOBAL_CONST
|
||||
else:
|
||||
return self.is_constant(node)
|
||||
|
||||
def python(self, code):
|
||||
"""
|
||||
Performs analysis on a block of python code.
|
||||
"""
|
||||
|
||||
nodes = ccache.ast_exec(code)
|
||||
|
||||
a = PyAnalysis(self)
|
||||
|
||||
for i in nodes:
|
||||
a.visit(i)
|
||||
|
||||
def parameters(self, parameters):
|
||||
"""
|
||||
Analyzes the parameters to the screen.
|
||||
"""
|
||||
|
||||
self.global_constant = DeltaSet(constants)
|
||||
|
||||
# As we have parameters, analyze with those parameters.
|
||||
|
||||
for name in parameters.parameters:
|
||||
self.mark_not_constant(name)
|
||||
|
||||
|
||||
class PyAnalysis(ast.NodeVisitor):
|
||||
"""
|
||||
This analyzes Python code to determine which variables should be
|
||||
marked const, and which should be marked non-const.
|
||||
"""
|
||||
|
||||
def __init__(self, analysis):
|
||||
|
||||
self.analysis = analysis
|
||||
|
||||
def visit_Name(self, node):
|
||||
|
||||
if isinstance(node.ctx, ast.AugStore):
|
||||
self.analysis.mark_not_constant(node.id)
|
||||
|
||||
elif isinstance(node.ctx, ast.Store):
|
||||
if self.analysis.control.const:
|
||||
self.analysis.mark_constant(node.id)
|
||||
else:
|
||||
self.analysis.mark_not_constant(node.id)
|
||||
|
||||
def visit_Assign(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AugAssign(self, node):
|
||||
|
||||
self.analysis.push_control(False, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_For(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.iter)
|
||||
|
||||
self.analysis.push_control(const=const, loop=True)
|
||||
old_const = self.analysis.control.const
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
if self.analysis.control.const != old_const:
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_While(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.test)
|
||||
|
||||
self.analysis.push_control(const=const, loop=True)
|
||||
old_const = self.analysis.control.const
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
if self.analysis.control.const != old_const:
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_If(self, node):
|
||||
const = self.analysis.is_constant(node.test)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
# The continue and break statements should be pretty rare, so if they
|
||||
# occur, we mark everything later in the loop as non-const.
|
||||
|
||||
def visit_Break(self, node):
|
||||
self.analysis.exit_loop()
|
||||
|
||||
def visit_Continue(self, node):
|
||||
self.analysis.exit_loop()
|
||||
|
||||
|
||||
class CompilerCache(object):
|
||||
"""
|
||||
Objects of this class are used to cache the compiliation of Python code.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.ast_eval_cache = { }
|
||||
self.ast_exec_cache = { }
|
||||
|
||||
# True if we've changed the caches.
|
||||
self.updated = False
|
||||
|
||||
# The version of this object.
|
||||
self.version = 1
|
||||
|
||||
def ast_eval_literal(self, expr):
|
||||
"""
|
||||
Compiles an expression into an AST.
|
||||
"""
|
||||
|
||||
if isinstance(expr, renpy.ast.PyExpr):
|
||||
filename = expr.filename
|
||||
linenumber = expr.linenumber
|
||||
else:
|
||||
filename = None
|
||||
linenumber = None
|
||||
|
||||
key = (expr, filename, linenumber)
|
||||
|
||||
rv = self.ast_eval_cache.get(key, None)
|
||||
|
||||
if rv is None:
|
||||
expr = py_compile(expr, 'eval', ast_node=True)
|
||||
|
||||
try:
|
||||
ast.literal_eval(expr)
|
||||
literal = True
|
||||
except Exception:
|
||||
literal = False
|
||||
|
||||
rv = (expr, literal)
|
||||
|
||||
self.ast_eval_cache[key] = rv
|
||||
self.updated = True
|
||||
|
||||
new_ccache.ast_eval_cache[key] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def ast_eval(self, expr):
|
||||
return self.ast_eval_literal(expr)[0]
|
||||
|
||||
def ast_exec(self, code):
|
||||
"""
|
||||
Compiles a block into an AST.
|
||||
"""
|
||||
|
||||
if isinstance(code, renpy.ast.PyExpr):
|
||||
key = (code, code.filename, code.linenumber)
|
||||
else:
|
||||
key = (code, None, None)
|
||||
|
||||
rv = self.ast_exec_cache.get(key, None)
|
||||
|
||||
if rv is None:
|
||||
rv = py_compile(code, 'exec', ast_node=True)
|
||||
self.ast_exec_cache[key] = rv
|
||||
self.updated = True
|
||||
|
||||
new_ccache.ast_exec_cache[key] = rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
ccache = CompilerCache()
|
||||
new_ccache = CompilerCache()
|
||||
|
||||
CACHE_FILENAME = "cache/pyanalysis.rpyb"
|
||||
|
||||
|
||||
def load_cache():
|
||||
if renpy.game.args.compile: # type: ignore
|
||||
return
|
||||
|
||||
try:
|
||||
with renpy.loader.load(CACHE_FILENAME) as f:
|
||||
c = loads(zlib.decompress(f.read()))
|
||||
|
||||
if c.version == ccache.version:
|
||||
ccache.ast_eval_cache.update(c.ast_eval_cache)
|
||||
ccache.ast_exec_cache.update(c.ast_exec_cache)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def save_cache():
|
||||
if not ccache.updated:
|
||||
return
|
||||
|
||||
if renpy.macapp:
|
||||
return
|
||||
|
||||
try:
|
||||
data = zlib.compress(dumps(new_ccache, True), 3)
|
||||
|
||||
with open(renpy.loader.get_path(CACHE_FILENAME), "wb") as f:
|
||||
f.write(data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
__all__ = [
|
||||
"always_constants",
|
||||
"Analysis",
|
||||
"ccache",
|
||||
"CompilerCache",
|
||||
"const",
|
||||
"constants",
|
||||
"Control",
|
||||
"DeltaSet",
|
||||
"GLOBAL_CONST",
|
||||
"load_cache",
|
||||
"LOCAL_CONST",
|
||||
"local_constants",
|
||||
"new_ccache",
|
||||
"not_const",
|
||||
"NOT_CONST",
|
||||
"not_constants",
|
||||
"pure",
|
||||
"pure_functions",
|
||||
"PyAnalysis",
|
||||
"save_cache",
|
||||
]
|
||||
@@ -1,861 +0,0 @@
|
||||
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
import renpy # @UnusedImport
|
||||
from renpy.python import py_compile
|
||||
|
||||
# Import the Python AST module, instead of the Ren'Py ast module.
|
||||
import ast
|
||||
|
||||
import zlib
|
||||
|
||||
from renpy.compat.pickle import loads, dumps
|
||||
|
||||
# The set of names that should be treated as constants.
|
||||
always_constants = { 'True', 'False', 'None' }
|
||||
|
||||
# The set of names that should be treated as pure functions.
|
||||
pure_functions = {
|
||||
# Python 3 builtins.
|
||||
'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytes', 'callable',
|
||||
'chr', 'complex', 'dict', 'dir', 'divmod', 'enumerate', 'filter', 'float',
|
||||
'format', 'frozenset', 'getattr', 'hasattr', 'hash', 'hex', 'int',
|
||||
'isinstance', 'issubclass', 'len', 'list', 'map', 'max', 'min', 'oct',
|
||||
'ord', 'pow', 'range', 'repr', 'reversed', 'round', 'set', 'slice', 'sorted',
|
||||
'str', 'sum', 'tuple', 'type', 'zip',
|
||||
|
||||
# minstore.py
|
||||
"_",
|
||||
"_p",
|
||||
"absolute",
|
||||
"position",
|
||||
"__renpy__list__",
|
||||
"__renpy__dict__",
|
||||
"__renpy__set__",
|
||||
|
||||
# defaultstore.py
|
||||
"ImageReference", "Image", "Frame", "Solid", "LiveComposite", "LiveCrop",
|
||||
"LiveTile", "Flatten", "Null", "Window", "Viewport", "DynamicDisplayable",
|
||||
"ConditionSwitch", "ShowingSwitch", "Transform", "Animation", "Movie",
|
||||
"Particles", "SnowBlossom", "Text", "ParameterizedText", "FontGroup",
|
||||
"Drag", "Alpha", "AlphaMask", "Position", "Pan", "Move", "Motion", "Revolve", "Zoom",
|
||||
"RotoZoom", "FactorZoom", "SizeZoom", "Fade", "Dissolve", "ImageDissolve",
|
||||
"AlphaDissolve", "CropMove", "PushMove", "Pixellate", "OldMoveTransition",
|
||||
"MoveTransition", "MoveFactory", "MoveIn", "MoveOut", "ZoomInOut",
|
||||
"RevolveInOut", "MultipleTransition", "ComposeTransition", "Pause",
|
||||
"SubTransition", "ADVSpeaker", "ADVCharacter", "Speaker", "Character",
|
||||
"DynamicCharacter", "Fixed", "HBox", "VBox", "Grid", "AlphaBlend", "At",
|
||||
"color", "Color",
|
||||
|
||||
# ui.py
|
||||
|
||||
"ui.returns",
|
||||
"ui.jumps",
|
||||
"ui.jumpsoutofcontext",
|
||||
"ui.callsinnewcontext",
|
||||
"ui.invokesinnewcontext",
|
||||
"ui.gamemenus",
|
||||
|
||||
# renpy.py
|
||||
|
||||
"renpy.version_string",
|
||||
"renpy.version_only",
|
||||
"renpy.version_tuple",
|
||||
"renpy.version_name",
|
||||
"renpy.license",
|
||||
}
|
||||
|
||||
constants = { "config", "style" } | always_constants | pure_functions
|
||||
|
||||
# A set of names that should not be treated as global constants.
|
||||
not_constants = set()
|
||||
|
||||
# The base set for the local constants.
|
||||
local_constants = set()
|
||||
|
||||
|
||||
def const(name):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a variable in the store to be constant.
|
||||
|
||||
A variable is constant if nothing can change its value, or any value
|
||||
reached by indexing it or accessing its attributes. Variables must
|
||||
remain constant out of define, init, and translate python blocks.
|
||||
|
||||
`name`
|
||||
A string giving the name of the variable to declare constant.
|
||||
"""
|
||||
|
||||
if name not in not_constants:
|
||||
constants.add(name)
|
||||
|
||||
|
||||
def not_const(name):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a name in the store to be not constant.
|
||||
|
||||
This undoes the effect of calls to :func:`renpy.const` and
|
||||
:func:`renpy.pure`.
|
||||
|
||||
`name`
|
||||
The name to declare not constant.
|
||||
"""
|
||||
|
||||
constants.discard(name)
|
||||
pure_functions.discard(name)
|
||||
not_constants.add(name)
|
||||
|
||||
|
||||
def pure(fn):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a function as pure. A pure function must always return the
|
||||
same value when it is called with the same arguments, outside of
|
||||
define, init, and translate python blocks.
|
||||
|
||||
`fn`
|
||||
The name of the function to declare pure. This may either be a string
|
||||
containing the name of the function, or the function itself.
|
||||
If a string is passed and the function is inside a module,
|
||||
this string should contain the module name with the dot.
|
||||
|
||||
Returns `fn`, allowing this function to be used as a decorator.
|
||||
"""
|
||||
|
||||
name = fn
|
||||
|
||||
if not isinstance(name, basestring):
|
||||
name = fn.__name__
|
||||
|
||||
module = fn.__module__
|
||||
name = module + "." + name
|
||||
|
||||
if name.startswith("store."):
|
||||
name = name[6:]
|
||||
|
||||
if name not in not_constants:
|
||||
pure_functions.add(name)
|
||||
constants.add(name)
|
||||
|
||||
return fn
|
||||
|
||||
|
||||
class Control(object):
|
||||
"""
|
||||
Represents control flow.
|
||||
|
||||
`const`
|
||||
True if this statement always executes.
|
||||
|
||||
`loop`
|
||||
True if this corresponds to a loop.
|
||||
|
||||
`imagemap`
|
||||
True if this control is in a non-constant imagemap.
|
||||
"""
|
||||
|
||||
def __init__(self, const, loop, imagemap):
|
||||
self.const = const
|
||||
self.loop = loop
|
||||
self.imagemap = imagemap
|
||||
|
||||
def __repr__(self):
|
||||
return "<Control const={0} loop={1} imagemap={2}>".format(self.const, self.loop, self.imagemap)
|
||||
|
||||
|
||||
# Three levels of constness.
|
||||
|
||||
# An expression is globally constant if it will evaluate to the same value
|
||||
# whenever it is run.
|
||||
GLOBAL_CONST = 2
|
||||
|
||||
# An expression is locally const if it will evaluate to the same value when
|
||||
# run in the same place - the same screen, with the same parameters, the same
|
||||
# statement, and the same iteration of a for loop.
|
||||
LOCAL_CONST = 1
|
||||
|
||||
# An expression is not const if it wilk change it's value.
|
||||
NOT_CONST = 0
|
||||
|
||||
|
||||
class DeltaSet(object):
|
||||
|
||||
def __init__(self, base, copy=None):
|
||||
"""
|
||||
Represents a set that stores its contents as differences from a base
|
||||
set.
|
||||
"""
|
||||
|
||||
self.base = base
|
||||
|
||||
if copy is not None:
|
||||
self.added = set(copy.added)
|
||||
self.removed = set(copy.removed)
|
||||
else:
|
||||
self.added = set()
|
||||
self.removed = set()
|
||||
|
||||
self.changed = False
|
||||
|
||||
def add(self, v):
|
||||
|
||||
if v in self.removed:
|
||||
self.removed.discard(v)
|
||||
self.changed = True
|
||||
elif v not in self.base and v not in self.added:
|
||||
self.added.add(v)
|
||||
self.changed = True
|
||||
|
||||
def discard(self, v):
|
||||
|
||||
if v in self.added:
|
||||
self.added.discard(v)
|
||||
self.changed = True
|
||||
elif v in self.base and v not in self.removed:
|
||||
self.removed.add(v)
|
||||
self.changed = True
|
||||
|
||||
def __contains__(self, v):
|
||||
return (v in self.added) or ((v in self.base) and (v not in self.removed))
|
||||
|
||||
def copy(self):
|
||||
return DeltaSet(self.base, self)
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
for i in self.base:
|
||||
if i not in self.removed:
|
||||
yield i
|
||||
|
||||
for i in self.added:
|
||||
yield i
|
||||
|
||||
|
||||
class Analysis(object):
|
||||
"""
|
||||
Represents the result of code analysis, and provides tools to perform
|
||||
code analysis.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
||||
# The parent context transcludes run in, or None if there is no parent
|
||||
# context.
|
||||
self.parent = parent
|
||||
|
||||
# Analyses of children, such a screens we use.
|
||||
self.children = { }
|
||||
|
||||
# The variables we consider to be not-constant.
|
||||
self.not_constant = DeltaSet(not_constants)
|
||||
|
||||
# Variables we consider to be locally constant.
|
||||
self.local_constant = DeltaSet(local_constants)
|
||||
|
||||
# Variables we consider to be globally constant.
|
||||
self.global_constant = DeltaSet(always_constants)
|
||||
|
||||
# The functions we consider to be pure.
|
||||
self.pure_functions = DeltaSet(pure_functions)
|
||||
|
||||
# Represents what we know about the current control.
|
||||
self.control = Control(True, False, False)
|
||||
|
||||
# The stack of const_flow values.
|
||||
self.control_stack = [ self.control ]
|
||||
|
||||
def get_child(self, identifier):
|
||||
if identifier in self.children:
|
||||
return self.children[identifier]
|
||||
|
||||
rv = Analysis(self)
|
||||
self.children[identifier] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def push_control(self, const=True, loop=False, imagemap=False):
|
||||
self.control = Control(self.control.const and const, loop, self.control.imagemap or imagemap)
|
||||
self.control_stack.append(self.control) # type: ignore
|
||||
|
||||
def pop_control(self):
|
||||
rv = self.control_stack.pop()
|
||||
self.control = self.control_stack[-1]
|
||||
return rv
|
||||
|
||||
def imagemap(self):
|
||||
"""
|
||||
Returns NOT_CONST if we're in a non-constant imagemap.
|
||||
"""
|
||||
|
||||
if self.control.imagemap:
|
||||
return NOT_CONST
|
||||
else:
|
||||
return GLOBAL_CONST
|
||||
|
||||
def exit_loop(self):
|
||||
"""
|
||||
Call this to indicate the current loop is being exited by the
|
||||
continue or break statements.
|
||||
"""
|
||||
|
||||
l = list(self.control_stack)
|
||||
l.reverse()
|
||||
|
||||
for i in l:
|
||||
i.const = False
|
||||
|
||||
if i.loop:
|
||||
break
|
||||
|
||||
def at_fixed_point(self):
|
||||
"""
|
||||
Returns True if we've reached a fixed point, where the analysis has
|
||||
not changed since the last time we called this function.
|
||||
"""
|
||||
|
||||
for i in self.children.values():
|
||||
if not i.at_fixed_point():
|
||||
return False
|
||||
|
||||
if (self.not_constant.changed or
|
||||
self.global_constant.changed or
|
||||
self.local_constant.changed or
|
||||
self.pure_functions.changed):
|
||||
|
||||
self.not_constant.changed = False
|
||||
self.global_constant.changed = False
|
||||
self.local_constant.changed = False
|
||||
self.pure_functions.changed = False
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def mark_constant(self, name):
|
||||
"""
|
||||
Marks `name` as a potential local constant.
|
||||
"""
|
||||
|
||||
if not name in self.not_constant:
|
||||
self.local_constant.add(name)
|
||||
self.global_constant.discard(name)
|
||||
self.pure_functions.discard(name)
|
||||
|
||||
def mark_not_constant(self, name):
|
||||
"""
|
||||
Marks `name` as definitely not-constant.
|
||||
"""
|
||||
|
||||
self.not_constant.add(name)
|
||||
|
||||
self.pure_functions.discard(name)
|
||||
self.local_constant.discard(name)
|
||||
self.global_constant.discard(name)
|
||||
|
||||
def is_constant(self, node):
|
||||
"""
|
||||
Returns true if `node` is constant for the purpose of screen
|
||||
language. Node should be a python AST node.
|
||||
|
||||
Screen language ignores object identity for the purposes of
|
||||
object equality.
|
||||
"""
|
||||
|
||||
def check_name(node):
|
||||
"""
|
||||
Check nodes that make up a name. This returns a pair:
|
||||
|
||||
* The first element is True if the node is constant, and False
|
||||
otherwise.
|
||||
* The second element is None if the node is constant or the name is
|
||||
not known, and the name otherwise.
|
||||
"""
|
||||
|
||||
if isinstance(node, ast.Name):
|
||||
const = NOT_CONST
|
||||
name = node.id
|
||||
|
||||
elif isinstance(node, ast.Attribute):
|
||||
const, name = check_name(node.value)
|
||||
|
||||
if name is not None:
|
||||
name = name + "." + node.attr
|
||||
|
||||
else:
|
||||
return check_node(node), None
|
||||
|
||||
if name in self.not_constant:
|
||||
return NOT_CONST, name
|
||||
elif name in self.global_constant:
|
||||
return GLOBAL_CONST, name
|
||||
elif name in self.local_constant:
|
||||
return LOCAL_CONST, name
|
||||
else:
|
||||
return const, name
|
||||
|
||||
def check_nodes(nodes):
|
||||
"""
|
||||
Checks a list of nodes for constness.
|
||||
"""
|
||||
|
||||
nodes = list(nodes)
|
||||
|
||||
if not nodes:
|
||||
return GLOBAL_CONST
|
||||
|
||||
return min(check_node(i) for i in nodes)
|
||||
|
||||
def check_node(node):
|
||||
"""
|
||||
When given `node`, part of a Python expression, returns how
|
||||
const the expression is.
|
||||
"""
|
||||
|
||||
# This handles children that do not exist.
|
||||
if node is None:
|
||||
return GLOBAL_CONST
|
||||
|
||||
# PY3: see if there are new node types.
|
||||
|
||||
|
||||
if isinstance(node, ast.Constant):
|
||||
return GLOBAL_CONST
|
||||
|
||||
elif isinstance(node, ast.BoolOp):
|
||||
return check_nodes(node.values)
|
||||
|
||||
elif isinstance(node, ast.NamedExpr):
|
||||
return check_node(node.value)
|
||||
|
||||
elif isinstance(node, ast.BinOp):
|
||||
return min(
|
||||
check_node(node.left),
|
||||
check_node(node.right),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
return check_node(node.operand)
|
||||
|
||||
|
||||
# ast.Lambda is NOT_CONST.
|
||||
|
||||
elif isinstance(node, ast.IfExp):
|
||||
return min(
|
||||
check_node(node.test),
|
||||
check_node(node.body),
|
||||
check_node(node.orelse),
|
||||
)
|
||||
|
||||
|
||||
elif isinstance(node, ast.Dict):
|
||||
return min(
|
||||
check_nodes(node.keys),
|
||||
check_nodes(node.values)
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Set):
|
||||
return check_nodes(node.elts)
|
||||
|
||||
|
||||
# ast.ListComp is NOT_CONST.
|
||||
# ast.SetComp is NOT_CONST.
|
||||
# ast.DictComp is NOT_CONST.
|
||||
|
||||
# ast.GeneratorExp is NOT_CONST.
|
||||
|
||||
# ast.Await is NOT_CONST.
|
||||
# ast.Yield is NOT_CONST.
|
||||
# ast.YieldFrom is NOT_CONST.
|
||||
|
||||
elif isinstance(node, ast.Compare):
|
||||
return min(
|
||||
check_node(node.left),
|
||||
check_nodes(node.comparators),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Call):
|
||||
const, name = check_name(node.func)
|
||||
|
||||
# The function must have a name, and must be declared pure.
|
||||
if (const != GLOBAL_CONST) or (name not in self.pure_functions):
|
||||
return NOT_CONST
|
||||
|
||||
return min(
|
||||
check_nodes(node.args),
|
||||
check_nodes(i.value for i in node.keywords),
|
||||
)
|
||||
|
||||
|
||||
elif isinstance(node, ast.FormattedValue):
|
||||
return min(
|
||||
check_node(node.value),
|
||||
check_node(node.format_spec),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.JoinedStr):
|
||||
return check_nodes(node.values)
|
||||
|
||||
elif isinstance(node, (ast.Attribute, ast.Name)):
|
||||
return check_name(node)[0]
|
||||
|
||||
elif isinstance(node, ast.Subscript):
|
||||
return min(
|
||||
check_node(node.value),
|
||||
check_node(node.slice),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Starred):
|
||||
return check_node(node.value)
|
||||
|
||||
elif isinstance(node, (ast.List, ast.Tuple)):
|
||||
return check_nodes(node.elts)
|
||||
|
||||
elif isinstance(node, ast.Slice):
|
||||
return min(
|
||||
check_node(node.lower),
|
||||
check_node(node.upper),
|
||||
check_node(node.step),
|
||||
)
|
||||
|
||||
return NOT_CONST
|
||||
|
||||
return check_node(node)
|
||||
|
||||
def is_constant_expr(self, expr):
|
||||
"""
|
||||
Compiles `expr` into an AST node, then returns the result of
|
||||
self.is_constant called on that node.
|
||||
"""
|
||||
|
||||
node, literal = ccache.ast_eval_literal(expr)
|
||||
|
||||
if literal:
|
||||
return GLOBAL_CONST
|
||||
else:
|
||||
return self.is_constant(node)
|
||||
|
||||
def python(self, code):
|
||||
"""
|
||||
Performs analysis on a block of python code.
|
||||
"""
|
||||
|
||||
nodes = ccache.ast_exec(code)
|
||||
|
||||
a = PyAnalysis(self)
|
||||
|
||||
for i in nodes:
|
||||
a.visit(i)
|
||||
|
||||
def parameters(self, parameters):
|
||||
"""
|
||||
Analyzes the parameters to the screen.
|
||||
"""
|
||||
|
||||
self.global_constant = DeltaSet(constants)
|
||||
|
||||
# As we have parameters, analyze with those parameters.
|
||||
|
||||
for name in parameters.parameters:
|
||||
self.mark_not_constant(name)
|
||||
|
||||
|
||||
class PyAnalysis(ast.NodeVisitor):
|
||||
"""
|
||||
This analyzes Python code to determine which variables should be
|
||||
marked const, and which should be marked non-const.
|
||||
"""
|
||||
|
||||
def __init__(self, analysis):
|
||||
|
||||
self.analysis = analysis
|
||||
|
||||
# Expressions that assign names.
|
||||
def visit_Name(self, node):
|
||||
|
||||
if isinstance(node.ctx, ast.AugStore):
|
||||
self.analysis.mark_not_constant(node.id)
|
||||
|
||||
elif isinstance(node.ctx, ast.Store):
|
||||
if self.analysis.control.const:
|
||||
self.analysis.mark_constant(node.id)
|
||||
else:
|
||||
self.analysis.mark_not_constant(node.id)
|
||||
|
||||
def visit_NamedExpr(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
|
||||
# Statements that assign names or control constness.
|
||||
def visit_FunctionDef(self, node):
|
||||
self.analysis.mark_constant(node.name)
|
||||
|
||||
def visit_AsyncFunctionDef(self, node):
|
||||
self.analysis.mark_constant(node.name)
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
self.analysis.mark_constant(node.name)
|
||||
|
||||
# Return can't assign a name.
|
||||
|
||||
# Delete doesn't assign a name - so it would be something else making
|
||||
# the name non-const, not delete.
|
||||
|
||||
|
||||
def visit_Assign(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AugAssign(self, node):
|
||||
|
||||
self.analysis.push_control(False, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AnnAssign(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_For(self, node): # type: (ast.For|ast.AsyncFor) -> None
|
||||
|
||||
const = self.analysis.is_constant(node.iter)
|
||||
|
||||
self.analysis.push_control(const=const, loop=True)
|
||||
old_const = self.analysis.control.const
|
||||
|
||||
self.generic_visit(node) # All nodes in the loop depend on node.test.
|
||||
|
||||
if self.analysis.control.const != old_const:
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AsyncFor(self, node):
|
||||
return self.visit_For(node)
|
||||
|
||||
def visit_While(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.test)
|
||||
|
||||
self.analysis.push_control(const=const, loop=True)
|
||||
old_const = self.analysis.control.const
|
||||
|
||||
self.generic_visit(node) # All nodes in the loop depend on node.test.
|
||||
|
||||
if self.analysis.control.const != old_const:
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_If(self, node):
|
||||
const = self.analysis.is_constant(node.test)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
# Nothing special for visit_With or visit_AsyncWith, when withitem is
|
||||
# defined as below.
|
||||
|
||||
def visit_withitem(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.context_expr)
|
||||
self.visit(node.context_expr)
|
||||
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
if node.optional_vars is not None:
|
||||
self.visit(node.optional_vars)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
# Match is barely implemented. We assume that it's always going to be
|
||||
# performed on something non-constant, which means that every variable
|
||||
# assigned inside the match is also non-constant. This is probably a
|
||||
# reasonable assumption.
|
||||
def visit_Match(self, node):
|
||||
self.analysis.push_control(False)
|
||||
self.generic_visit(node)
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_Try(self, node):
|
||||
|
||||
for i in node.handlers:
|
||||
if i.name:
|
||||
self.analysis.mark_not_constant(i.name)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
# Import and Import from can only assign to a variable in a way that
|
||||
# keeps it constant.
|
||||
|
||||
# Global and NonLocal only make sense inside Python functions, and we don't
|
||||
# analyze Python functions.
|
||||
|
||||
# Expr can be ignored, as it can't assign.
|
||||
|
||||
# The continue and break statements should be pretty rare, so if they
|
||||
# occur, we mark everything later in the loop as non-const.
|
||||
def visit_Break(self, node):
|
||||
self.analysis.exit_loop()
|
||||
|
||||
def visit_Continue(self, node):
|
||||
self.analysis.exit_loop()
|
||||
|
||||
|
||||
class CompilerCache(object):
|
||||
"""
|
||||
Objects of this class are used to cache the compiliation of Python code.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.ast_eval_cache = { }
|
||||
self.ast_exec_cache = { }
|
||||
|
||||
# True if we've changed the caches.
|
||||
self.updated = False
|
||||
|
||||
# The version of this object.
|
||||
self.version = 1
|
||||
|
||||
def ast_eval_literal(self, expr):
|
||||
"""
|
||||
Compiles an expression into an AST.
|
||||
"""
|
||||
|
||||
if isinstance(expr, renpy.ast.PyExpr):
|
||||
filename = expr.filename
|
||||
linenumber = expr.linenumber
|
||||
else:
|
||||
filename = None
|
||||
linenumber = None
|
||||
|
||||
key = (expr, filename, linenumber)
|
||||
|
||||
rv = self.ast_eval_cache.get(key, None)
|
||||
|
||||
if rv is None:
|
||||
expr = py_compile(expr, 'eval', ast_node=True)
|
||||
|
||||
try:
|
||||
ast.literal_eval(expr)
|
||||
literal = True
|
||||
except Exception:
|
||||
literal = False
|
||||
|
||||
rv = (expr, literal)
|
||||
|
||||
self.ast_eval_cache[key] = rv
|
||||
self.updated = True
|
||||
|
||||
new_ccache.ast_eval_cache[key] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def ast_eval(self, expr):
|
||||
return self.ast_eval_literal(expr)[0]
|
||||
|
||||
def ast_exec(self, code):
|
||||
"""
|
||||
Compiles a block into an AST.
|
||||
"""
|
||||
|
||||
if isinstance(code, renpy.ast.PyExpr):
|
||||
key = (code, code.filename, code.linenumber)
|
||||
else:
|
||||
key = (code, None, None)
|
||||
|
||||
rv = self.ast_exec_cache.get(key, None)
|
||||
|
||||
if rv is None:
|
||||
rv = py_compile(code, 'exec', ast_node=True)
|
||||
self.ast_exec_cache[key] = rv
|
||||
self.updated = True
|
||||
|
||||
new_ccache.ast_exec_cache[key] = rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
ccache = CompilerCache()
|
||||
new_ccache = CompilerCache()
|
||||
|
||||
CACHE_FILENAME = "cache/py3analysis.rpyb"
|
||||
|
||||
|
||||
def load_cache():
|
||||
if renpy.game.args.compile: # type: ignore
|
||||
return
|
||||
|
||||
try:
|
||||
with renpy.loader.load(CACHE_FILENAME) as f:
|
||||
c = loads(zlib.decompress(f.read()))
|
||||
|
||||
if c.version == ccache.version:
|
||||
ccache.ast_eval_cache.update(c.ast_eval_cache)
|
||||
ccache.ast_exec_cache.update(c.ast_exec_cache)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def save_cache():
|
||||
if not ccache.updated:
|
||||
return
|
||||
|
||||
if renpy.macapp:
|
||||
return
|
||||
|
||||
try:
|
||||
data = zlib.compress(dumps(new_ccache, True), 3)
|
||||
|
||||
with open(renpy.loader.get_path(CACHE_FILENAME), "wb") as f:
|
||||
f.write(data)
|
||||
except Exception:
|
||||
pass
|
||||
+845
-4
@@ -23,7 +23,848 @@ from __future__ import division, absolute_import, with_statement, print_function
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
if PY2:
|
||||
from renpy.py2analysis import *
|
||||
else:
|
||||
from renpy.py3analysis import *
|
||||
import builtins
|
||||
|
||||
import renpy # @UnusedImport
|
||||
from renpy.python import py_compile
|
||||
|
||||
# Import the Python AST module, instead of the Ren'Py ast module.
|
||||
import ast
|
||||
|
||||
import zlib
|
||||
|
||||
from renpy.compat.pickle import loads, dumps
|
||||
|
||||
# The set of names that should be treated as constants.
|
||||
always_constants = { 'True', 'False', 'None' }
|
||||
|
||||
# The set of names that should be treated as pure functions.
|
||||
pure_functions = {
|
||||
# Python 3 builtins.
|
||||
'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytes', 'callable',
|
||||
'chr', 'complex', 'dict', 'dir', 'divmod', 'enumerate', 'filter', 'float',
|
||||
'format', 'frozenset', 'getattr', 'hasattr', 'hash', 'hex', 'int',
|
||||
'isinstance', 'issubclass', 'len', 'list', 'map', 'max', 'min', 'oct',
|
||||
'ord', 'pow', 'range', 'repr', 'reversed', 'round', 'set', 'slice', 'sorted',
|
||||
'str', 'sum', 'tuple', 'type', 'zip',
|
||||
|
||||
# minstore.py
|
||||
"_",
|
||||
"_p",
|
||||
"absolute",
|
||||
"position",
|
||||
"__renpy__list__",
|
||||
"__renpy__dict__",
|
||||
"__renpy__set__",
|
||||
|
||||
# defaultstore.py
|
||||
"ImageReference", "Image", "Frame", "Solid", "LiveComposite", "LiveCrop",
|
||||
"LiveTile", "Flatten", "Null", "Window", "Viewport", "DynamicDisplayable",
|
||||
"ConditionSwitch", "ShowingSwitch", "Transform", "Animation", "Movie",
|
||||
"Particles", "SnowBlossom", "Text", "ParameterizedText", "FontGroup",
|
||||
"Drag", "Alpha", "AlphaMask", "Position", "Pan", "Move", "Motion", "Revolve", "Zoom",
|
||||
"RotoZoom", "FactorZoom", "SizeZoom", "Fade", "Dissolve", "ImageDissolve",
|
||||
"AlphaDissolve", "CropMove", "PushMove", "Pixellate", "OldMoveTransition",
|
||||
"MoveTransition", "MoveFactory", "MoveIn", "MoveOut", "ZoomInOut",
|
||||
"RevolveInOut", "MultipleTransition", "ComposeTransition", "Pause",
|
||||
"SubTransition", "ADVSpeaker", "ADVCharacter", "Speaker", "Character",
|
||||
"DynamicCharacter", "Fixed", "HBox", "VBox", "Grid", "AlphaBlend", "At",
|
||||
"color", "Color",
|
||||
|
||||
# ui.py
|
||||
|
||||
"ui.returns",
|
||||
"ui.jumps",
|
||||
"ui.jumpsoutofcontext",
|
||||
"ui.callsinnewcontext",
|
||||
"ui.invokesinnewcontext",
|
||||
"ui.gamemenus",
|
||||
|
||||
# renpy.py
|
||||
|
||||
"renpy.version_string",
|
||||
"renpy.version_only",
|
||||
"renpy.version_tuple",
|
||||
"renpy.version_name",
|
||||
"renpy.license",
|
||||
}
|
||||
|
||||
constants = { "config", "style" } | always_constants | pure_functions
|
||||
|
||||
# A set of names that should not be treated as global constants.
|
||||
not_constants = set()
|
||||
|
||||
# The base set for the local constants.
|
||||
local_constants = set()
|
||||
|
||||
|
||||
def const(name):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a variable in the store to be constant.
|
||||
|
||||
A variable is constant if nothing can change its value, or any value
|
||||
reached by indexing it or accessing its attributes. Variables must
|
||||
remain constant out of define, init, and translate python blocks.
|
||||
|
||||
`name`
|
||||
A string giving the name of the variable to declare constant.
|
||||
"""
|
||||
|
||||
if name not in not_constants:
|
||||
constants.add(name)
|
||||
|
||||
|
||||
def not_const(name):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a name in the store to be not constant.
|
||||
|
||||
This undoes the effect of calls to :func:`renpy.const` and
|
||||
:func:`renpy.pure`.
|
||||
|
||||
`name`
|
||||
The name to declare not constant.
|
||||
"""
|
||||
|
||||
constants.discard(name)
|
||||
pure_functions.discard(name)
|
||||
not_constants.add(name)
|
||||
|
||||
|
||||
def pure(fn):
|
||||
"""
|
||||
:doc: const
|
||||
|
||||
Declares a function as pure. A pure function must always return the
|
||||
same value when it is called with the same arguments, outside of
|
||||
define, init, and translate python blocks.
|
||||
|
||||
`fn`
|
||||
The name of the function to declare pure. This may either be a string
|
||||
containing the name of the function, or the function itself.
|
||||
If a string is passed and the function is inside a module,
|
||||
this string should contain the module name with the dot.
|
||||
|
||||
Returns `fn`, allowing this function to be used as a decorator.
|
||||
"""
|
||||
|
||||
name = fn
|
||||
|
||||
if not isinstance(name, basestring):
|
||||
name = fn.__name__
|
||||
|
||||
module = fn.__module__
|
||||
name = module + "." + name
|
||||
|
||||
if name.startswith("store."):
|
||||
name = name[6:]
|
||||
|
||||
if name not in not_constants:
|
||||
pure_functions.add(name)
|
||||
constants.add(name)
|
||||
|
||||
return fn
|
||||
|
||||
|
||||
class Control(object):
|
||||
"""
|
||||
Represents control flow.
|
||||
|
||||
`const`
|
||||
True if this statement always executes.
|
||||
|
||||
`loop`
|
||||
True if this corresponds to a loop.
|
||||
|
||||
`imagemap`
|
||||
True if this control is in a non-constant imagemap.
|
||||
"""
|
||||
|
||||
def __init__(self, const, loop, imagemap):
|
||||
self.const = const
|
||||
self.loop = loop
|
||||
self.imagemap = imagemap
|
||||
|
||||
def __repr__(self):
|
||||
return "<Control const={0} loop={1} imagemap={2}>".format(self.const, self.loop, self.imagemap)
|
||||
|
||||
|
||||
# Three levels of constness.
|
||||
|
||||
# An expression is globally constant if it will evaluate to the same value
|
||||
# whenever it is run.
|
||||
GLOBAL_CONST = 2
|
||||
|
||||
# An expression is locally const if it will evaluate to the same value when
|
||||
# run in the same place - the same screen, with the same parameters, the same
|
||||
# statement, and the same iteration of a for loop.
|
||||
LOCAL_CONST = 1
|
||||
|
||||
# An expression is not const if it wilk change it's value.
|
||||
NOT_CONST = 0
|
||||
|
||||
|
||||
class DeltaSet(object):
|
||||
|
||||
def __init__(self, base, copy=None):
|
||||
"""
|
||||
Represents a set that stores its contents as differences from a base
|
||||
set.
|
||||
"""
|
||||
|
||||
self.base = base
|
||||
|
||||
if copy is not None:
|
||||
self.added = set(copy.added)
|
||||
self.removed = set(copy.removed)
|
||||
else:
|
||||
self.added = set()
|
||||
self.removed = set()
|
||||
|
||||
self.changed = False
|
||||
|
||||
def add(self, v):
|
||||
|
||||
if v in self.removed:
|
||||
self.removed.discard(v)
|
||||
self.changed = True
|
||||
elif v not in self.base and v not in self.added:
|
||||
self.added.add(v)
|
||||
self.changed = True
|
||||
|
||||
def discard(self, v):
|
||||
|
||||
if v in self.added:
|
||||
self.added.discard(v)
|
||||
self.changed = True
|
||||
elif v in self.base and v not in self.removed:
|
||||
self.removed.add(v)
|
||||
self.changed = True
|
||||
|
||||
def __contains__(self, v):
|
||||
return (v in self.added) or ((v in self.base) and (v not in self.removed))
|
||||
|
||||
def copy(self):
|
||||
return DeltaSet(self.base, self)
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
for i in self.base:
|
||||
if i not in self.removed:
|
||||
yield i
|
||||
|
||||
for i in self.added:
|
||||
yield i
|
||||
|
||||
|
||||
class Analysis(object):
|
||||
"""
|
||||
Represents the result of code analysis, and provides tools to perform
|
||||
code analysis.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
||||
# The parent context transcludes run in, or None if there is no parent
|
||||
# context.
|
||||
self.parent = parent
|
||||
|
||||
# Analyses of children, such a screens we use.
|
||||
self.children = { }
|
||||
|
||||
# The variables we consider to be not-constant.
|
||||
self.not_constant = DeltaSet(not_constants)
|
||||
|
||||
# Variables we consider to be locally constant.
|
||||
self.local_constant = DeltaSet(local_constants)
|
||||
|
||||
# Variables we consider to be globally constant.
|
||||
self.global_constant = DeltaSet(always_constants)
|
||||
|
||||
# The functions we consider to be pure.
|
||||
self.pure_functions = DeltaSet(pure_functions)
|
||||
|
||||
# Represents what we know about the current control.
|
||||
self.control = Control(True, False, False)
|
||||
|
||||
# The stack of const_flow values.
|
||||
self.control_stack = [ self.control ]
|
||||
|
||||
def get_child(self, identifier):
|
||||
if identifier in self.children:
|
||||
return self.children[identifier]
|
||||
|
||||
rv = Analysis(self)
|
||||
self.children[identifier] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def push_control(self, const=True, loop=False, imagemap=False):
|
||||
self.control = Control(self.control.const and const, loop, self.control.imagemap or imagemap)
|
||||
self.control_stack.append(self.control) # type: ignore
|
||||
|
||||
def pop_control(self):
|
||||
rv = self.control_stack.pop()
|
||||
self.control = self.control_stack[-1]
|
||||
return rv
|
||||
|
||||
def imagemap(self):
|
||||
"""
|
||||
Returns NOT_CONST if we're in a non-constant imagemap.
|
||||
"""
|
||||
|
||||
if self.control.imagemap:
|
||||
return NOT_CONST
|
||||
else:
|
||||
return GLOBAL_CONST
|
||||
|
||||
def exit_loop(self):
|
||||
"""
|
||||
Call this to indicate the current loop is being exited by the
|
||||
continue or break statements.
|
||||
"""
|
||||
|
||||
l = list(self.control_stack)
|
||||
l.reverse()
|
||||
|
||||
for i in l:
|
||||
i.const = False
|
||||
|
||||
if i.loop:
|
||||
break
|
||||
|
||||
def at_fixed_point(self):
|
||||
"""
|
||||
Returns True if we've reached a fixed point, where the analysis has
|
||||
not changed since the last time we called this function.
|
||||
"""
|
||||
|
||||
for i in self.children.values():
|
||||
if not i.at_fixed_point():
|
||||
return False
|
||||
|
||||
if (self.not_constant.changed or
|
||||
self.global_constant.changed or
|
||||
self.local_constant.changed or
|
||||
self.pure_functions.changed):
|
||||
|
||||
self.not_constant.changed = False
|
||||
self.global_constant.changed = False
|
||||
self.local_constant.changed = False
|
||||
self.pure_functions.changed = False
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def mark_constant(self, name):
|
||||
"""
|
||||
Marks `name` as a potential local constant.
|
||||
"""
|
||||
|
||||
if not name in self.not_constant:
|
||||
self.local_constant.add(name)
|
||||
self.global_constant.discard(name)
|
||||
self.pure_functions.discard(name)
|
||||
|
||||
def mark_not_constant(self, name):
|
||||
"""
|
||||
Marks `name` as definitely not-constant.
|
||||
"""
|
||||
|
||||
self.not_constant.add(name)
|
||||
|
||||
self.pure_functions.discard(name)
|
||||
self.local_constant.discard(name)
|
||||
self.global_constant.discard(name)
|
||||
|
||||
|
||||
def _check_name(self, node):
|
||||
"""
|
||||
Check nodes that make up a name. This returns a pair:
|
||||
|
||||
* The first element is True if the node is constant, and False
|
||||
otherwise.
|
||||
* The second element is None if the node is constant or the name is
|
||||
not known, and the name otherwise.
|
||||
"""
|
||||
|
||||
if isinstance(node, ast.Name):
|
||||
const = NOT_CONST
|
||||
name = node.id
|
||||
|
||||
elif isinstance(node, ast.Attribute):
|
||||
const, name = self._check_name(node.value)
|
||||
|
||||
if name is not None:
|
||||
name = name + "." + node.attr
|
||||
|
||||
else:
|
||||
return self._check_node(node), None
|
||||
|
||||
if name in self.not_constant:
|
||||
return NOT_CONST, name
|
||||
elif name in self.global_constant:
|
||||
return GLOBAL_CONST, name
|
||||
elif name in self.local_constant:
|
||||
return LOCAL_CONST, name
|
||||
else:
|
||||
return const, name
|
||||
|
||||
def _check_nodes(self, nodes):
|
||||
"""
|
||||
Checks a list of nodes for constness.
|
||||
"""
|
||||
|
||||
nodes = list(nodes)
|
||||
|
||||
if not nodes:
|
||||
return GLOBAL_CONST
|
||||
|
||||
return min(self._check_node(i) for i in nodes)
|
||||
|
||||
def _check_node(self, node):
|
||||
"""
|
||||
When given `node`, part of a Python expression, returns how
|
||||
const the expression is.
|
||||
"""
|
||||
|
||||
# This handles children that do not exist.
|
||||
if node is None:
|
||||
return GLOBAL_CONST
|
||||
|
||||
# PY3: see if there are new node types.
|
||||
|
||||
|
||||
if isinstance(node, ast.Constant):
|
||||
return GLOBAL_CONST
|
||||
|
||||
elif isinstance(node, ast.BoolOp):
|
||||
return self._check_nodes(node.values)
|
||||
|
||||
elif isinstance(node, ast.NamedExpr):
|
||||
return self._check_node(node.value)
|
||||
|
||||
elif isinstance(node, ast.BinOp):
|
||||
return min(
|
||||
self._check_node(node.left),
|
||||
self._check_node(node.right),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
return self._check_node(node.operand)
|
||||
|
||||
|
||||
# ast.Lambda is NOT_CONST.
|
||||
|
||||
elif isinstance(node, ast.IfExp):
|
||||
return min(
|
||||
self._check_node(node.test),
|
||||
self._check_node(node.body),
|
||||
self._check_node(node.orelse),
|
||||
)
|
||||
|
||||
|
||||
elif isinstance(node, ast.Dict):
|
||||
return min(
|
||||
self._check_nodes(node.keys),
|
||||
self._check_nodes(node.values)
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Set):
|
||||
return self._check_nodes(node.elts)
|
||||
|
||||
|
||||
# ast.ListComp is NOT_CONST.
|
||||
# ast.SetComp is NOT_CONST.
|
||||
# ast.DictComp is NOT_CONST.
|
||||
|
||||
# ast.GeneratorExp is NOT_CONST.
|
||||
|
||||
# ast.Await is NOT_CONST.
|
||||
# ast.Yield is NOT_CONST.
|
||||
# ast.YieldFrom is NOT_CONST.
|
||||
|
||||
elif isinstance(node, ast.Compare):
|
||||
return min(
|
||||
self._check_node(node.left),
|
||||
self._check_nodes(node.comparators),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Call):
|
||||
const, name = self._check_name(node.func)
|
||||
|
||||
# The function must have a name, and must be declared pure.
|
||||
if (const != GLOBAL_CONST) or (name not in self.pure_functions):
|
||||
return NOT_CONST
|
||||
|
||||
return min(
|
||||
self._check_nodes(node.args),
|
||||
self._check_nodes(i.value for i in node.keywords),
|
||||
)
|
||||
|
||||
|
||||
elif isinstance(node, ast.FormattedValue):
|
||||
return min(
|
||||
self._check_node(node.value),
|
||||
self._check_node(node.format_spec),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.JoinedStr):
|
||||
return self._check_nodes(node.values)
|
||||
|
||||
elif isinstance(node, (ast.Attribute, ast.Name)):
|
||||
return self._check_name(node)[0]
|
||||
|
||||
elif isinstance(node, ast.Subscript):
|
||||
return min(
|
||||
self._check_node(node.value),
|
||||
self._check_node(node.slice),
|
||||
)
|
||||
|
||||
elif isinstance(node, ast.Starred):
|
||||
return self._check_node(node.value)
|
||||
|
||||
elif isinstance(node, (ast.List, ast.Tuple)):
|
||||
return self._check_nodes(node.elts)
|
||||
|
||||
elif isinstance(node, ast.Slice):
|
||||
return min(
|
||||
self._check_node(node.lower),
|
||||
self._check_node(node.upper),
|
||||
self._check_node(node.step),
|
||||
)
|
||||
|
||||
return NOT_CONST
|
||||
|
||||
def is_constant(self, node):
|
||||
"""
|
||||
Returns true if `node` is constant for the purpose of screen
|
||||
language. Node should be a python AST node.
|
||||
|
||||
Screen language ignores object identity for the purposes of
|
||||
object equality.
|
||||
"""
|
||||
|
||||
return self._check_node(node)
|
||||
|
||||
def is_constant_expr(self, expr):
|
||||
"""
|
||||
Compiles `expr` into an AST node, then returns the result of
|
||||
self.is_constant called on that node.
|
||||
"""
|
||||
|
||||
node, literal = ccache.ast_eval_literal(expr)
|
||||
|
||||
if literal:
|
||||
return GLOBAL_CONST
|
||||
else:
|
||||
return self.is_constant(node)
|
||||
|
||||
def python(self, code):
|
||||
"""
|
||||
Performs analysis on a block of python code.
|
||||
"""
|
||||
|
||||
nodes = ccache.ast_exec(code)
|
||||
|
||||
a = PyAnalysis(self)
|
||||
|
||||
for i in nodes:
|
||||
a.visit(i)
|
||||
|
||||
def parameters(self, parameters):
|
||||
"""
|
||||
Analyzes the parameters to the screen.
|
||||
"""
|
||||
|
||||
self.global_constant = DeltaSet(constants)
|
||||
|
||||
# As we have parameters, analyze with those parameters.
|
||||
|
||||
for name in parameters.parameters:
|
||||
self.mark_not_constant(name)
|
||||
|
||||
|
||||
class PyAnalysis(ast.NodeVisitor):
|
||||
"""
|
||||
This analyzes Python code to determine which variables should be
|
||||
marked const, and which should be marked non-const.
|
||||
"""
|
||||
|
||||
def __init__(self, analysis):
|
||||
|
||||
self.analysis = analysis
|
||||
|
||||
# Expressions that assign names.
|
||||
def visit_Name(self, node):
|
||||
|
||||
if isinstance(node.ctx, ast.AugStore):
|
||||
self.analysis.mark_not_constant(node.id)
|
||||
|
||||
elif isinstance(node.ctx, ast.Store):
|
||||
if self.analysis.control.const:
|
||||
self.analysis.mark_constant(node.id)
|
||||
else:
|
||||
self.analysis.mark_not_constant(node.id)
|
||||
|
||||
def visit_NamedExpr(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
|
||||
# Statements that assign names or control constness.
|
||||
def visit_FunctionDef(self, node):
|
||||
self.analysis.mark_constant(node.name)
|
||||
|
||||
def visit_AsyncFunctionDef(self, node):
|
||||
self.analysis.mark_constant(node.name)
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
self.analysis.mark_constant(node.name)
|
||||
|
||||
# Return can't assign a name.
|
||||
|
||||
# Delete doesn't assign a name - so it would be something else making
|
||||
# the name non-const, not delete.
|
||||
|
||||
|
||||
def visit_Assign(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AugAssign(self, node):
|
||||
|
||||
self.analysis.push_control(False, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AnnAssign(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.value)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_For(self, node): # type: (ast.For|ast.AsyncFor) -> None
|
||||
|
||||
const = self.analysis.is_constant(node.iter)
|
||||
|
||||
self.analysis.push_control(const=const, loop=True)
|
||||
old_const = self.analysis.control.const
|
||||
|
||||
self.generic_visit(node) # All nodes in the loop depend on node.test.
|
||||
|
||||
if self.analysis.control.const != old_const:
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_AsyncFor(self, node):
|
||||
return self.visit_For(node)
|
||||
|
||||
def visit_While(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.test)
|
||||
|
||||
self.analysis.push_control(const=const, loop=True)
|
||||
old_const = self.analysis.control.const
|
||||
|
||||
self.generic_visit(node) # All nodes in the loop depend on node.test.
|
||||
|
||||
if self.analysis.control.const != old_const:
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
def visit_If(self, node):
|
||||
const = self.analysis.is_constant(node.test)
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
# Nothing special for visit_With or visit_AsyncWith, when withitem is
|
||||
# defined as below.
|
||||
|
||||
def visit_withitem(self, node):
|
||||
|
||||
const = self.analysis.is_constant(node.context_expr)
|
||||
self.visit(node.context_expr)
|
||||
|
||||
self.analysis.push_control(const, False)
|
||||
|
||||
if node.optional_vars is not None:
|
||||
self.visit(node.optional_vars)
|
||||
|
||||
self.analysis.pop_control()
|
||||
|
||||
# Match is barely implemented. We assume that it's always going to be
|
||||
# performed on something non-constant, which means that every variable
|
||||
# assigned inside the match is also non-constant. This is probably a
|
||||
# reasonable assumption.
|
||||
def visit_MatchMapping(self, node):
|
||||
if node.rest:
|
||||
self.analysis.mark_not_constant(node.rest)
|
||||
|
||||
def visit_MatchStar(self, node):
|
||||
if node.name is not None:
|
||||
self.analysis.mark_not_constant(node.name)
|
||||
|
||||
def visit_MatchAs(self, node):
|
||||
if node.name is not None:
|
||||
self.analysis.mark_not_constant(node.name)
|
||||
|
||||
def visit_Try(self, node):
|
||||
|
||||
for i in node.handlers:
|
||||
if i.name:
|
||||
self.analysis.mark_not_constant(i.name)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
# Import and Import from can only assign to a variable in a way that
|
||||
# keeps it constant.
|
||||
|
||||
# Global and NonLocal only make sense inside Python functions, and we don't
|
||||
# analyze Python functions.
|
||||
|
||||
# Expr can be ignored, as it can't assign.
|
||||
|
||||
# The continue and break statements should be pretty rare, so if they
|
||||
# occur, we mark everything later in the loop as non-const.
|
||||
def visit_Break(self, node):
|
||||
self.analysis.exit_loop()
|
||||
|
||||
def visit_Continue(self, node):
|
||||
self.analysis.exit_loop()
|
||||
|
||||
|
||||
|
||||
class CompilerCache(object):
|
||||
"""
|
||||
Objects of this class are used to cache the compiliation of Python code.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.ast_eval_cache = { }
|
||||
self.ast_exec_cache = { }
|
||||
|
||||
# True if we've changed the caches.
|
||||
self.updated = False
|
||||
|
||||
# The version of this object.
|
||||
self.version = 1
|
||||
|
||||
def ast_eval_literal(self, expr):
|
||||
"""
|
||||
Compiles an expression into an AST.
|
||||
"""
|
||||
|
||||
if isinstance(expr, renpy.ast.PyExpr):
|
||||
filename = expr.filename
|
||||
linenumber = expr.linenumber
|
||||
else:
|
||||
filename = None
|
||||
linenumber = None
|
||||
|
||||
key = (expr, filename, linenumber)
|
||||
|
||||
rv = self.ast_eval_cache.get(key, None)
|
||||
|
||||
if rv is None:
|
||||
expr = py_compile(expr, 'eval', ast_node=True)
|
||||
|
||||
try:
|
||||
ast.literal_eval(expr)
|
||||
literal = True
|
||||
except Exception:
|
||||
literal = False
|
||||
|
||||
rv = (expr, literal)
|
||||
|
||||
self.ast_eval_cache[key] = rv
|
||||
self.updated = True
|
||||
|
||||
new_ccache.ast_eval_cache[key] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def ast_eval(self, expr):
|
||||
return self.ast_eval_literal(expr)[0]
|
||||
|
||||
def ast_exec(self, code):
|
||||
"""
|
||||
Compiles a block into an AST.
|
||||
"""
|
||||
|
||||
if isinstance(code, renpy.ast.PyExpr):
|
||||
key = (code, code.filename, code.linenumber)
|
||||
else:
|
||||
key = (code, None, None)
|
||||
|
||||
rv = self.ast_exec_cache.get(key, None)
|
||||
|
||||
if rv is None:
|
||||
rv = py_compile(code, 'exec', ast_node=True)
|
||||
self.ast_exec_cache[key] = rv
|
||||
self.updated = True
|
||||
|
||||
new_ccache.ast_exec_cache[key] = rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
ccache = CompilerCache()
|
||||
new_ccache = CompilerCache()
|
||||
|
||||
CACHE_FILENAME = "cache/py3analysis.rpyb"
|
||||
|
||||
|
||||
def load_cache():
|
||||
if renpy.game.args.compile: # type: ignore
|
||||
return
|
||||
|
||||
try:
|
||||
with renpy.loader.load(CACHE_FILENAME) as f:
|
||||
c = loads(zlib.decompress(f.read()))
|
||||
|
||||
if c.version == ccache.version:
|
||||
ccache.ast_eval_cache.update(c.ast_eval_cache)
|
||||
ccache.ast_exec_cache.update(c.ast_exec_cache)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def save_cache():
|
||||
if not ccache.updated:
|
||||
return
|
||||
|
||||
if renpy.macapp:
|
||||
return
|
||||
|
||||
try:
|
||||
data = zlib.compress(dumps(new_ccache, True), 3)
|
||||
|
||||
with open(renpy.loader.get_path(CACHE_FILENAME), "wb") as f:
|
||||
f.write(data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
+234
-171
@@ -26,7 +26,7 @@
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
from typing import Optional, Any
|
||||
from typing import Optional, Any, cast
|
||||
import contextlib
|
||||
|
||||
# Import the python ast module, not ours.
|
||||
@@ -351,13 +351,6 @@ def reset_store_changes(name):
|
||||
# Code that replaces literals will calls to magic constructors.
|
||||
|
||||
|
||||
def b(s):
|
||||
if PY2:
|
||||
return s.encode("utf-8")
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
class LoadedVariables(ast.NodeVisitor):
|
||||
"""
|
||||
This is used to implement find_loaded_variables.
|
||||
@@ -368,15 +361,21 @@ class LoadedVariables(ast.NodeVisitor):
|
||||
self.loaded.add(node.id)
|
||||
elif isinstance(node.ctx, ast.Store):
|
||||
self.stored.add(node.id)
|
||||
elif PY2 and isinstance(node.ctx, ast.Param):
|
||||
# there's no guarantee that ast.Param will keep existing in future versions of python3
|
||||
# it's only present in asts made in py2
|
||||
self.stored.add(node.id)
|
||||
|
||||
if not PY2:
|
||||
# we could remove this if, but the method wouldn't be called in py2 anyway
|
||||
def visit_arg(self, node):
|
||||
self.stored.add(node.arg)
|
||||
def visit_MatchMapping(self, node):
|
||||
if node.rest:
|
||||
self.stored.add(node.rest)
|
||||
|
||||
def visit_MatchStar(self, node):
|
||||
if node.name is not None:
|
||||
self.stored.add(node.name)
|
||||
|
||||
def visit_MatchAs(self, node):
|
||||
if node.name is not None:
|
||||
self.stored.add(node.name)
|
||||
|
||||
def visit_arg(self, node):
|
||||
self.stored.add(node.arg)
|
||||
|
||||
def find(self, node):
|
||||
self.loaded = set()
|
||||
@@ -444,6 +443,34 @@ class WrapFormattedValue(ast.NodeTransformer):
|
||||
wrap_formatted_value = WrapFormattedValue().visit
|
||||
|
||||
|
||||
class FindStarredMatchPatterns(ast.NodeVisitor):
|
||||
"""
|
||||
Given a match patten, return a list of (name, type) tuples, where type is "dict" or "list".
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.vars = [ ]
|
||||
|
||||
def visit_MatchStar(self, node: ast.MatchStar) -> Any:
|
||||
self.generic_visit(node)
|
||||
if node.name is not None:
|
||||
self.vars.append((node.name, "list"))
|
||||
|
||||
def visit_MatchMapping(self, node: ast.MatchMapping) -> Any:
|
||||
self.generic_visit(node)
|
||||
if node.rest is not None:
|
||||
self.vars.append((node.rest, "dict"))
|
||||
|
||||
def find_starred_match_patterns(node):
|
||||
"""
|
||||
Given a match pattern, return a list of (name, type) tuples, where type is "dict" or "list".
|
||||
"""
|
||||
|
||||
visitor = FindStarredMatchPatterns()
|
||||
visitor.visit(node)
|
||||
return visitor.vars
|
||||
|
||||
|
||||
class WrapNode(ast.NodeTransformer):
|
||||
|
||||
|
||||
@@ -470,53 +497,26 @@ class WrapNode(ast.NodeTransformer):
|
||||
call_args =[ ]
|
||||
|
||||
for var in variables:
|
||||
if PY2:
|
||||
lambda_args.append(ast.Name(id=var, ctx=ast.Param()))
|
||||
else:
|
||||
lambda_args.append(ast.arg(arg=var))
|
||||
|
||||
lambda_args.append(ast.arg(arg=var))
|
||||
call_args.append(ast.Name(id=var, ctx=ast.Load()))
|
||||
|
||||
if PY2:
|
||||
|
||||
return ast.Call(
|
||||
func=ast.Lambda(
|
||||
args=ast.arguments(
|
||||
args=lambda_args,
|
||||
vararg=None,
|
||||
kwarg=None,
|
||||
defaults=[]
|
||||
),
|
||||
body=node,
|
||||
return ast.Call(
|
||||
func=ast.Lambda(
|
||||
args=ast.arguments(
|
||||
posonlyargs=[ ],
|
||||
args=lambda_args,
|
||||
kwonlyargs=[ ],
|
||||
kw_defaults=[ ],
|
||||
defaults=[ ],
|
||||
),
|
||||
args=call_args,
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
return ast.Call(
|
||||
func=ast.Lambda(
|
||||
args=ast.arguments(
|
||||
posonlyargs=[ ],
|
||||
args=lambda_args,
|
||||
kwonlyargs=[ ],
|
||||
kw_defaults=[ ],
|
||||
defaults=[ ],
|
||||
),
|
||||
body=node,
|
||||
),
|
||||
args=call_args,
|
||||
keywords=[ ],
|
||||
)
|
||||
body=node,
|
||||
),
|
||||
args=call_args,
|
||||
keywords=[ ],
|
||||
)
|
||||
|
||||
def wrap_starred_assign(self, n, targets):
|
||||
|
||||
if PY2:
|
||||
return n
|
||||
|
||||
starred = find_starred_variables(targets)
|
||||
|
||||
if not starred:
|
||||
@@ -528,15 +528,13 @@ class WrapNode(ast.NodeTransformer):
|
||||
|
||||
call = ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__list__"),
|
||||
id="__renpy__list__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Name(id=var, ctx=ast.Load())
|
||||
],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
assign = ast.Assign(
|
||||
targets=[ ast.Name(id=var, ctx=ast.Store()) ],
|
||||
@@ -554,9 +552,6 @@ class WrapNode(ast.NodeTransformer):
|
||||
|
||||
def wrap_starred_for(self, node):
|
||||
|
||||
if PY2:
|
||||
return node
|
||||
|
||||
starred = find_starred_variables([ node.target ])
|
||||
|
||||
if not starred:
|
||||
@@ -566,15 +561,13 @@ class WrapNode(ast.NodeTransformer):
|
||||
|
||||
call = ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__list__"),
|
||||
id="__renpy__list__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Name(id=var, ctx=ast.Load())
|
||||
],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
assign = ast.Assign(
|
||||
targets=[ ast.Name(id=var, ctx=ast.Store()) ],
|
||||
@@ -588,9 +581,6 @@ class WrapNode(ast.NodeTransformer):
|
||||
|
||||
def wrap_starred_with(self, node):
|
||||
|
||||
if PY2:
|
||||
return node
|
||||
|
||||
optional_vars = [ ]
|
||||
|
||||
for i in node.items:
|
||||
@@ -609,15 +599,13 @@ class WrapNode(ast.NodeTransformer):
|
||||
|
||||
call = ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__list__"),
|
||||
id="__renpy__list__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Name(id=var, ctx=ast.Load())
|
||||
],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
assign = ast.Assign(
|
||||
targets=[ ast.Name(id=var, ctx=ast.Store()) ],
|
||||
@@ -628,6 +616,52 @@ class WrapNode(ast.NodeTransformer):
|
||||
|
||||
return node
|
||||
|
||||
def wrap_match_case(self, node):
|
||||
|
||||
starred = find_starred_match_patterns(node.pattern)
|
||||
if not starred:
|
||||
return node
|
||||
|
||||
for var, kind in reversed(starred):
|
||||
|
||||
if kind == "list":
|
||||
call = ast.Call(
|
||||
func=ast.Name(
|
||||
id="__renpy__list__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Name(id=var, ctx=ast.Load())
|
||||
],
|
||||
keywords=[ ])
|
||||
|
||||
assign = ast.Assign(
|
||||
targets=[ ast.Name(id=var, ctx=ast.Store()) ],
|
||||
value=call,
|
||||
)
|
||||
|
||||
node.body.insert(0, assign)
|
||||
|
||||
elif kind == "dict":
|
||||
call = ast.Call(
|
||||
func=ast.Name(
|
||||
id="__renpy__dict__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
ast.Name(id=var, ctx=ast.Load())
|
||||
],
|
||||
keywords=[ ])
|
||||
|
||||
assign = ast.Assign(
|
||||
targets=[ ast.Name(id=var, ctx=ast.Store()) ],
|
||||
value=call,
|
||||
)
|
||||
|
||||
node.body.insert(0, assign)
|
||||
|
||||
return node
|
||||
|
||||
def visit_Assign(self, n):
|
||||
n = self.generic_visit(n)
|
||||
return self.wrap_starred_assign(n, n.targets) # type: ignore
|
||||
@@ -656,7 +690,7 @@ class WrapNode(ast.NodeTransformer):
|
||||
n = self.generic_visit(n)
|
||||
|
||||
if not n.bases: # type: ignore
|
||||
n.bases.append(ast.Name(id=b("object"), ctx=ast.Load())) # type: ignore
|
||||
n.bases.append(ast.Name(id="object", ctx=ast.Load())) # type: ignore
|
||||
|
||||
return n
|
||||
|
||||
@@ -666,80 +700,75 @@ class WrapNode(ast.NodeTransformer):
|
||||
def visit_SetComp(self, n):
|
||||
return ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__set__"),
|
||||
id="__renpy__set__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ self.wrap_generator(n) ],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
def visit_Set(self, n):
|
||||
|
||||
return ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__set__"),
|
||||
id="__renpy__set__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ self.generic_visit(n) ],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
def visit_ListComp(self, n):
|
||||
|
||||
return ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__list__"),
|
||||
id="__renpy__list__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ self.wrap_generator(n) ],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
def visit_List(self, n):
|
||||
|
||||
if not isinstance(n.ctx, ast.Load):
|
||||
return self.generic_visit(n)
|
||||
|
||||
return ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__list__"),
|
||||
id="__renpy__list__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ self.generic_visit(n) ],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
def visit_DictComp(self, n):
|
||||
return ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__dict__"),
|
||||
id="__renpy__dict__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ self.wrap_generator(n) ],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
keywords=[ ])
|
||||
|
||||
def visit_Dict(self, n):
|
||||
|
||||
return ast.Call(
|
||||
func=ast.Name(
|
||||
id=b("__renpy__dict__"),
|
||||
id="__renpy__dict__",
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ self.generic_visit(n) ],
|
||||
keywords=[ ],
|
||||
starargs=None,
|
||||
kwargs=None)
|
||||
|
||||
keywords=[ ])
|
||||
|
||||
def visit_FormattedValue(self, n):
|
||||
n = wrap_formatted_value(n)
|
||||
return self.generic_visit(n)
|
||||
|
||||
def visit_Match(self, node : ast.Match) -> Any:
|
||||
n : ast.Match = self.generic_visit(node) # type: ignore
|
||||
n.cases = [ self.wrap_match_case(i) for i in n.cases ]
|
||||
return n
|
||||
|
||||
|
||||
|
||||
wrap_node = WrapNode()
|
||||
|
||||
|
||||
@@ -847,35 +876,86 @@ py_compile_cache = { }
|
||||
old_py_compile_cache = { }
|
||||
|
||||
|
||||
def fix_locations(node, lineno, col_offset):
|
||||
def fix_locations(
|
||||
node: ast.AST,
|
||||
lineno_increment: int = 0,
|
||||
col_offset: int = 0,
|
||||
strip_ends: bool = False,
|
||||
_parent_offset=(0, 0),
|
||||
_first=True,
|
||||
):
|
||||
"""
|
||||
Assigns locations to the given node, and all of its children, adding
|
||||
any missing line numbers and column offsets.
|
||||
This function does `ast.increment_lineno`, `ast.fix_missing_locations`
|
||||
and `[end_]col_offset` increments in one pass.
|
||||
|
||||
`lineno_increment`
|
||||
Amount of lines to increment `lineno` by.
|
||||
|
||||
`col_offset`
|
||||
0-indexed offset of source code. This should be used in case
|
||||
source code have extra indent relative to compiled source.
|
||||
|
||||
`strip_ends`
|
||||
If `True`, removes `end_lineno` and `end_col_offset`
|
||||
so traceback does not render incorrect carets for given nodes tree.
|
||||
"""
|
||||
|
||||
start = max(
|
||||
(lineno, col_offset),
|
||||
(getattr(node, "lineno", None) or 1, getattr(node, "col_offset", None) or 0)
|
||||
)
|
||||
if _first:
|
||||
if not isinstance(node, (ast.Module, ast.Expression)):
|
||||
raise ValueError(f"'fix_locations' called with unsupported top level node: {node}")
|
||||
|
||||
lineno, col_offset = start
|
||||
# TypeIgnore is a special case where lineno is not an attribute
|
||||
# but rather a field of the node itself.
|
||||
if isinstance(node, ast.TypeIgnore):
|
||||
node.lineno = getattr(node, 'lineno', 0) + lineno_increment
|
||||
return
|
||||
|
||||
node.lineno = lineno
|
||||
node.col_offset = col_offset
|
||||
parent_lineno, parent_col_offset = _parent_offset
|
||||
|
||||
ends = [ start, (getattr(node, "end_lineno", None) or 1, getattr(node, "end_col_offset", None) or 0) ]
|
||||
# lineno and col_offset must exist on every node.
|
||||
# If there are none, we assume ast creator knows what they
|
||||
# are doing and we can just assign to parent values.
|
||||
if 'lineno' in node._attributes:
|
||||
node = cast("ast.expr", node)
|
||||
node.lineno = getattr(node, 'lineno', parent_lineno)
|
||||
parent_lineno = node.lineno
|
||||
node.lineno += lineno_increment
|
||||
|
||||
if 'col_offset' in node._attributes:
|
||||
node = cast("ast.expr", node)
|
||||
node.col_offset = getattr(node, 'col_offset', parent_col_offset)
|
||||
parent_col_offset = node.col_offset
|
||||
node.col_offset += col_offset
|
||||
|
||||
for child in ast.iter_child_nodes(node):
|
||||
fix_locations(child, lineno, col_offset)
|
||||
ends.append((child.end_lineno, child.end_col_offset))
|
||||
fix_locations(
|
||||
child,
|
||||
lineno_increment,
|
||||
col_offset,
|
||||
strip_ends,
|
||||
(parent_lineno, parent_col_offset),
|
||||
False)
|
||||
|
||||
end = max(ends)
|
||||
# end_lineno and end_col_offset are optional, and we remove them if
|
||||
# strip_ends is True (for generated code that doesn't have source).
|
||||
if 'end_lineno' in node._attributes:
|
||||
node = cast("ast.expr", node)
|
||||
if strip_ends:
|
||||
node.end_lineno = None
|
||||
else:
|
||||
end_lineno = getattr(node, 'end_lineno', None)
|
||||
node.end_lineno = (end_lineno or parent_lineno) + lineno_increment
|
||||
|
||||
node.end_lineno = end[0]
|
||||
node.end_col_offset = end[1]
|
||||
if 'end_col_offset' in node._attributes:
|
||||
node = cast("ast.expr", node)
|
||||
if strip_ends:
|
||||
node.end_col_offset = None
|
||||
else:
|
||||
end_col_offset = getattr(node, 'end_col_offset', None)
|
||||
node.end_col_offset = (end_col_offset or parent_col_offset) + col_offset
|
||||
|
||||
|
||||
def quote_eval(s):
|
||||
def quote_eval(s: str):
|
||||
"""
|
||||
Quotes a string for `eval`. This is necessary when it's in certain places,
|
||||
like as part of an argument string. We need to stick a single backslash
|
||||
@@ -968,18 +1048,17 @@ def quote_eval(s):
|
||||
compile_filename = ""
|
||||
|
||||
|
||||
def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=True, py=None):
|
||||
def py_compile(source, mode, filename='<none>', lineno=1, col_offset=None, ast_node=False, cache=True, py=None):
|
||||
"""
|
||||
Compiles the given source code using the supplied codegenerator.
|
||||
Compiles the given source code using the supplied code generator.
|
||||
Lists, List Comprehensions, and Dictionaries are wrapped when
|
||||
appropriate.
|
||||
|
||||
`source`
|
||||
The source code, as a either a string, pyexpr, or ast module
|
||||
node.
|
||||
The source code, as a either a string or PyExpr.
|
||||
|
||||
`mode`
|
||||
One of "exec" or "eval".
|
||||
One of "exec", "hide" or "eval".
|
||||
|
||||
`filename`
|
||||
The filename the source comes from. If a pyexpr is given, the
|
||||
@@ -987,7 +1066,12 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
|
||||
`lineno`
|
||||
The line number of the first line of source code. If a pyexpr is
|
||||
given, the filename embedded in the pyexpr is used.
|
||||
given, the lineno embedded in the pyexpr is used.
|
||||
|
||||
`col_offset`
|
||||
If not None, the column offset of the first line of source code,
|
||||
otherwise offset information is stripped from the source code.
|
||||
If a pyexpr is given, the col_offset embedded in the pyexpr is used.
|
||||
|
||||
`ast_node`
|
||||
Rather than returning compiled bytecode, returns the AST object
|
||||
@@ -1000,9 +1084,6 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
if ast_node:
|
||||
cache = False
|
||||
|
||||
if isinstance(source, ast.Module):
|
||||
return compile(source, filename, mode)
|
||||
|
||||
if isinstance(source, renpy.ast.PyExpr):
|
||||
filename = source.filename
|
||||
lineno = source.linenumber
|
||||
@@ -1011,10 +1092,7 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
py = source.py
|
||||
|
||||
if py is None:
|
||||
if PY2:
|
||||
py = 2
|
||||
else:
|
||||
py = 3
|
||||
py = 3
|
||||
|
||||
if cache:
|
||||
key = (lineno, filename, str(source), mode, renpy.script.MAGIC)
|
||||
@@ -1052,8 +1130,10 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
if mode == "eval":
|
||||
source = quote_eval(source)
|
||||
|
||||
line_offset = lineno - 1
|
||||
compile_filename = filename
|
||||
line_offset = lineno - 1
|
||||
code_offset = col_offset or 0
|
||||
strip_ends = col_offset is None
|
||||
|
||||
try:
|
||||
|
||||
@@ -1063,42 +1143,38 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
py_mode = mode
|
||||
|
||||
flags = file_compiler_flags.get(filename, 0)
|
||||
flags |= new_compile_flags
|
||||
|
||||
if (not PY2) or flags:
|
||||
try:
|
||||
with save_warnings():
|
||||
# filename should not be a valid file name, so Python tokenizer
|
||||
# function 'get_error_line_from_tokenizer_buffers' does not try
|
||||
# to read from the file to get the size of the buffer of error
|
||||
# line.
|
||||
tree = compile(source, "<tree>", py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
except SyntaxError as orig_e:
|
||||
# Set back filename to correct one.
|
||||
# For what it worth, '_PyPegen_raise_error_known_location'
|
||||
# fixes lineno, col_offset and end_col_offset, but not end_lineno.
|
||||
# We don't use it, so whatever.
|
||||
orig_e.filename = filename
|
||||
|
||||
flags |= new_compile_flags
|
||||
# Inserting artificial tokens can broke locations.
|
||||
strip_ends = True
|
||||
|
||||
try:
|
||||
fixed_source = renpy.compat.fixes.fix_tokens(source)
|
||||
with save_warnings():
|
||||
tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
except SyntaxError as orig_e:
|
||||
|
||||
try:
|
||||
fixed_source = renpy.compat.fixes.fix_tokens(source)
|
||||
with save_warnings():
|
||||
tree = compile(fixed_source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
except Exception:
|
||||
raise orig_e
|
||||
|
||||
else:
|
||||
|
||||
try:
|
||||
flags = new_compile_flags
|
||||
with save_warnings():
|
||||
tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
tree = compile(fixed_source, "<tree>", py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
except Exception:
|
||||
flags = old_compile_flags
|
||||
source = escape_unicode(source)
|
||||
with save_warnings():
|
||||
tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
raise orig_e
|
||||
|
||||
tree = wrap_node.visit(tree)
|
||||
|
||||
if mode == "hide":
|
||||
wrap_hide(tree)
|
||||
|
||||
fix_locations(tree, 1, 0)
|
||||
ast.increment_lineno(tree, lineno - 1)
|
||||
fix_locations(tree, line_offset, code_offset, strip_ends)
|
||||
|
||||
line_offset = 0
|
||||
|
||||
@@ -1111,7 +1187,8 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
except SyntaxError as orig_e:
|
||||
try:
|
||||
tree = renpy.compat.fixes.fix_ast(tree)
|
||||
fix_locations(tree, 1, 0)
|
||||
# Creating artificial nodes can broke locations.
|
||||
fix_locations(tree, strip_ends=True)
|
||||
with save_warnings():
|
||||
rv = compile(tree, filename, py_mode, flags, 1)
|
||||
except Exception:
|
||||
@@ -1252,20 +1329,6 @@ class StoreProxy(object):
|
||||
def method_unpickle(obj, name):
|
||||
return getattr(obj, name)
|
||||
|
||||
if PY2:
|
||||
|
||||
# Code for pickling bound methods.
|
||||
def method_pickle(method):
|
||||
name = method.im_func.__name__
|
||||
|
||||
obj = method.im_self
|
||||
|
||||
if obj is None:
|
||||
obj = method.im_class
|
||||
|
||||
return method_unpickle, (obj, name)
|
||||
|
||||
copyreg.pickle(types.MethodType, method_pickle)
|
||||
|
||||
# Code for pickling modules.
|
||||
|
||||
|
||||
+28
-73
@@ -65,18 +65,10 @@ copyreg._reconstructor = _reconstructor # type: ignore
|
||||
# this to check to see if a background-save is valid.
|
||||
mutate_flag = True
|
||||
|
||||
# In Python 2 functools.wraps does not check for the existence of WRAPPER_ASSIGNMENTS elements,
|
||||
# so for the C-defined methods we have AttributeError: 'method_descriptor' object has no attribute '__module__'.
|
||||
# To work around this, we only keep attributes that surely exist.
|
||||
if PY2:
|
||||
def _method_wrapper(method):
|
||||
return functools.wraps(method, ("__name__", "__doc__"), ())
|
||||
else:
|
||||
_method_wrapper = functools.wraps # type: ignore
|
||||
|
||||
def mutator(method):
|
||||
|
||||
@_method_wrapper(method)
|
||||
@functools.wraps(method)
|
||||
def do_mutation(self, *args, **kwargs):
|
||||
|
||||
global mutate_flag
|
||||
@@ -176,11 +168,7 @@ class RevertableList(list):
|
||||
list.__init__(self, *args)
|
||||
|
||||
__delitem__ = mutator(list.__delitem__)
|
||||
if PY2:
|
||||
__delslice__ = mutator(list.__delslice__) # type: ignore
|
||||
__setitem__ = mutator(list.__setitem__)
|
||||
if PY2:
|
||||
__setslice__ = mutator(list.__setslice__) # type: ignore
|
||||
__iadd__ = mutator(list.__iadd__)
|
||||
__imul__ = mutator(list.__imul__)
|
||||
append = mutator(list.append)
|
||||
@@ -193,7 +181,7 @@ class RevertableList(list):
|
||||
|
||||
def wrapper(method): # type: ignore
|
||||
|
||||
@_method_wrapper(method)
|
||||
@functools.wraps(method)
|
||||
def newmethod(*args, **kwargs):
|
||||
l = method(*args, **kwargs) # type: ignore
|
||||
if l is NotImplemented:
|
||||
@@ -203,8 +191,6 @@ class RevertableList(list):
|
||||
return newmethod
|
||||
|
||||
__add__ = wrapper(list.__add__) # type: ignore
|
||||
if PY2:
|
||||
__getslice__ = wrapper(list.__getslice__) # type: ignore
|
||||
__mul__ = wrapper(list.__mul__) # type: ignore
|
||||
__rmul__ = wrapper(list.__rmul__) # type: ignore
|
||||
|
||||
@@ -288,58 +274,31 @@ class RevertableDict(dict):
|
||||
setdefault = mutator(dict.setdefault)
|
||||
update = mutator(dict.update)
|
||||
|
||||
if PY2:
|
||||
itervalues = dict.values
|
||||
iterkeys = dict.keys
|
||||
iteritems = dict.items
|
||||
|
||||
def keys(self):
|
||||
rv = dict.keys(self)
|
||||
def has_key(self, key):
|
||||
return (key in self)
|
||||
|
||||
if (sys._getframe(1).f_code.co_flags & FUTURE_FLAGS) != FUTURE_FLAGS:
|
||||
rv = RevertableList(rv)
|
||||
# https://peps.python.org/pep-0584 methods
|
||||
def __or__(self, other):
|
||||
if not isinstance(other, dict):
|
||||
return NotImplemented
|
||||
rv = RevertableDict(self)
|
||||
rv.update(other)
|
||||
return rv
|
||||
|
||||
return rv
|
||||
def __ror__(self, other):
|
||||
if not isinstance(other, dict):
|
||||
return NotImplemented
|
||||
rv = RevertableDict(other)
|
||||
rv.update(self)
|
||||
return rv
|
||||
|
||||
def values(self):
|
||||
rv = dict.values(self)
|
||||
|
||||
if (sys._getframe(1).f_code.co_flags & FUTURE_FLAGS) != FUTURE_FLAGS:
|
||||
rv = RevertableList(rv)
|
||||
|
||||
return rv
|
||||
|
||||
def items(self):
|
||||
rv = dict.items(self)
|
||||
|
||||
if (sys._getframe(1).f_code.co_flags & FUTURE_FLAGS) != FUTURE_FLAGS:
|
||||
rv = RevertableList(rv)
|
||||
|
||||
return rv
|
||||
|
||||
else:
|
||||
itervalues = dict.values
|
||||
iterkeys = dict.keys
|
||||
iteritems = dict.items
|
||||
|
||||
def has_key(self, key):
|
||||
return (key in self)
|
||||
|
||||
# https://peps.python.org/pep-0584 methods
|
||||
def __or__(self, other):
|
||||
if not isinstance(other, dict):
|
||||
return NotImplemented
|
||||
rv = RevertableDict(self)
|
||||
rv.update(other)
|
||||
return rv
|
||||
|
||||
def __ror__(self, other):
|
||||
if not isinstance(other, dict):
|
||||
return NotImplemented
|
||||
rv = RevertableDict(other)
|
||||
rv.update(self)
|
||||
return rv
|
||||
|
||||
def __ior__(self, other):
|
||||
self.update(other)
|
||||
return self
|
||||
def __ior__(self, other):
|
||||
self.update(other)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
rv = RevertableDict()
|
||||
@@ -429,7 +388,7 @@ class RevertableSet(set):
|
||||
|
||||
def wrapper(method): # type: ignore
|
||||
|
||||
@_method_wrapper(method)
|
||||
@functools.wraps(method)
|
||||
def newmethod(*args, **kwargs):
|
||||
rv = method(*args, **kwargs) # type: ignore
|
||||
if isinstance(rv, set):
|
||||
@@ -548,7 +507,7 @@ class MultiRevertable(object):
|
||||
|
||||
def checkpointing(method):
|
||||
|
||||
@_method_wrapper(method)
|
||||
@functools.wraps(method)
|
||||
def do_checkpoint(self, *args, **kwargs):
|
||||
|
||||
renpy.game.context().force_checkpoint = True
|
||||
@@ -560,7 +519,7 @@ def checkpointing(method):
|
||||
|
||||
def list_wrapper(method):
|
||||
|
||||
@_method_wrapper(method)
|
||||
@functools.wraps(method)
|
||||
def newmethod(*args, **kwargs):
|
||||
l = method(*args, **kwargs)
|
||||
return RevertableList(l)
|
||||
@@ -592,10 +551,7 @@ class RollbackRandom(random.Random):
|
||||
|
||||
setstate = checkpointing(mutator(random.Random.setstate))
|
||||
|
||||
if PY2:
|
||||
jumpahead = checkpointing(mutator(random.Random.jumpahead)) # type: ignore
|
||||
else:
|
||||
choices = list_wrapper(random.Random.choices)
|
||||
choices = list_wrapper(random.Random.choices)
|
||||
sample = list_wrapper(random.Random.sample)
|
||||
|
||||
getrandbits = checkpointing(mutator(random.Random.getrandbits))
|
||||
@@ -623,8 +579,7 @@ class DetRandom(random.Random):
|
||||
super(DetRandom, self).__init__()
|
||||
self.stack = [ ]
|
||||
|
||||
if not PY2:
|
||||
choices = list_wrapper(random.Random.choices)
|
||||
choices = list_wrapper(random.Random.choices)
|
||||
sample = list_wrapper(random.Random.sample)
|
||||
|
||||
def random(self):
|
||||
|
||||
+1
-4
@@ -47,10 +47,7 @@ import renpy
|
||||
class StoreDeleted(object):
|
||||
|
||||
def __reduce__(self):
|
||||
if PY2:
|
||||
return b"deleted"
|
||||
else:
|
||||
return "deleted"
|
||||
return "deleted"
|
||||
|
||||
|
||||
deleted = StoreDeleted()
|
||||
|
||||
+14
-30
@@ -47,20 +47,10 @@ script_version = renpy.script_version
|
||||
# The version of the bytecode cache.
|
||||
BYTECODE_VERSION = 1
|
||||
|
||||
# The python magic code.
|
||||
if PY2:
|
||||
import heapq
|
||||
import imp
|
||||
MAGIC = imp.get_magic()
|
||||
from importlib.util import MAGIC_NUMBER as MAGIC
|
||||
|
||||
# Change this to force a recompile when required.
|
||||
MAGIC += b'_v2.1'
|
||||
|
||||
else:
|
||||
from importlib.util import MAGIC_NUMBER as MAGIC
|
||||
|
||||
# Change this to force a recompile when required.
|
||||
MAGIC += b'_v3.1'
|
||||
# Change this to force a recompile when required.
|
||||
MAGIC += b'_v3.1'
|
||||
|
||||
# A string at the start of each rpycv2 file.
|
||||
RPYC2_HEADER = b"RENPY RPC2"
|
||||
@@ -223,10 +213,7 @@ class Script(object):
|
||||
|
||||
base, ext = os.path.splitext(short_fn)
|
||||
|
||||
if PY2:
|
||||
hex_checksum = checksum[:8].encode("hex")
|
||||
else:
|
||||
hex_checksum = checksum[:8].hex()
|
||||
hex_checksum = checksum[:8].hex()
|
||||
|
||||
target_fn = os.path.join(
|
||||
backupdir,
|
||||
@@ -573,12 +560,13 @@ class Script(object):
|
||||
if renpy.config.allow_duplicate_labels:
|
||||
return
|
||||
|
||||
import linecache
|
||||
self.duplicate_labels.append(
|
||||
u'The label {} is defined twice, at File "{}", line {}:\n{}and File "{}", line {}:\n{}'.format(
|
||||
bad_name, old_node.filename, old_node.linenumber,
|
||||
renpy.lexer.get_line_text(old_node.filename, old_node.linenumber),
|
||||
linecache.getline(old_node.filename, old_node.linenumber),
|
||||
bad_node.filename, bad_node.linenumber,
|
||||
renpy.lexer.get_line_text(bad_node.filename, bad_node.linenumber),
|
||||
linecache.getline(bad_node.filename, bad_node.linenumber),
|
||||
))
|
||||
|
||||
self.update_bytecode()
|
||||
@@ -1028,18 +1016,14 @@ class Script(object):
|
||||
code = renpy.python.py_compile_eval_bytecode(i.source, filename=i.location[0], lineno=i.location[1], py=i.py)
|
||||
|
||||
except SyntaxError as e:
|
||||
|
||||
text = e.text
|
||||
|
||||
if text is None:
|
||||
text = ''
|
||||
|
||||
assert e.filename is not None
|
||||
assert e.lineno is not None
|
||||
pem = renpy.parser.ParseError(
|
||||
filename=e.filename,
|
||||
number=e.lineno,
|
||||
msg=e.msg,
|
||||
line=text,
|
||||
pos=e.offset)
|
||||
e.msg,
|
||||
e.filename,
|
||||
e.lineno, e.offset,
|
||||
e.text,
|
||||
e.end_lineno, e.end_offset)
|
||||
|
||||
renpy.parser.parse_errors.append(pem.message)
|
||||
|
||||
|
||||
+5
-1
@@ -457,7 +457,11 @@ class ScaledImageFont(ImageFont):
|
||||
w, h = v.get_size()
|
||||
nw = scale(w)
|
||||
nh = scale(h)
|
||||
self.chars[k] = renpy.display.scale.smoothscale(v, (nw, nh))
|
||||
|
||||
if renpy.config.nearest_neighbor:
|
||||
self.chars[k] = renpy.display.pgrender.transform_scale(v, (nw, nh))
|
||||
else:
|
||||
self.chars[k] = renpy.display.scale.smoothscale(v, (nw, nh))
|
||||
|
||||
|
||||
def register_sfont(name=None, size=None, bold=False, italics=False, underline=False,
|
||||
|
||||
@@ -198,10 +198,10 @@ class ScriptTranslator(object):
|
||||
if n.identifier in self.default_translates:
|
||||
old_node = self.default_translates[n.identifier]
|
||||
|
||||
renpy.lexer.ParseError(n.filename, n.linenumber, "Line with id %s appears twice. The other line is %s:%d" % (
|
||||
n.identifier,
|
||||
old_node.filename, old_node.linenumber)
|
||||
).defer("duplicate_id")
|
||||
renpy.lexer.ParseError(
|
||||
f"Line with id {n.identifier} appears twice. "
|
||||
f"The other line is {old_node.filename}:{old_node.linenumber}",
|
||||
n.filename, n.linenumber,).defer("duplicate_id")
|
||||
|
||||
self.default_translates[n.identifier] = n
|
||||
self.file_translates[filename].append((label, n))
|
||||
|
||||
+1
-4
@@ -442,10 +442,7 @@ screen = None # type: renpy.display.screen.ScreenDisplayable|None
|
||||
class Wrapper(renpy.object.Object):
|
||||
|
||||
def __reduce__(self):
|
||||
if PY2:
|
||||
return bytes(self.name) # type: ignore
|
||||
else:
|
||||
return self.name
|
||||
return self.name
|
||||
|
||||
def __init__(self, function, one=False, many=False, imagemap=False, replaces=False, style=None, **kwargs):
|
||||
|
||||
|
||||
@@ -2,14 +2,22 @@
|
||||
# This builds out of date modules using the default C compiler, and then
|
||||
# runs them.
|
||||
|
||||
set -e
|
||||
|
||||
export RENPY_CYTHON=cython
|
||||
|
||||
try () {
|
||||
"$@" || exit -1
|
||||
}
|
||||
|
||||
ROOT="$(dirname $(realpath $0))"
|
||||
QUIET=${RENPY_QUIET- --quiet}
|
||||
|
||||
renpy_branch=$(git branch --show-current)
|
||||
pushd "$ROOT/pygame_sdl2" >/dev/null
|
||||
pygame_sdl2_branch=$(git branch --show-current)
|
||||
popd >/dev/null
|
||||
|
||||
if [ "$renpy_branch" != "$pygame_sdl2_branch" ]; then
|
||||
echo "warning: Ren'Py branch: $renpy_branch, pygame_sdl2 branch: $pygame_sdl2_branch"
|
||||
fi
|
||||
|
||||
if [ -n "$RENPY_COVERAGE" ]; then
|
||||
variant="renpy-coverage"
|
||||
else
|
||||
@@ -25,22 +33,20 @@ if [ -z "$VIRTUAL_ENV" ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PY_VERSION=$(python -c 'import sys; print(sys.version_info.major)')
|
||||
|
||||
if [ $PY_VERSION != "2" ]; then
|
||||
BUILD_J="-j $(nproc)"
|
||||
ADAPT_TO_SETUPTOOLS="--old-and-unmanageable"
|
||||
fi
|
||||
|
||||
ROOT="$(dirname $(realpath $0))"
|
||||
BUILD_J="-j $(nproc)"
|
||||
ADAPT_TO_SETUPTOOLS="--old-and-unmanageable"
|
||||
|
||||
setup () {
|
||||
pushd $1 >/dev/null
|
||||
|
||||
try python setup.py $QUIET \
|
||||
python setup.py $QUIET \
|
||||
build -b build/lib.$variant -t build/tmp.$variant $BUILD_J \
|
||||
$RENPY_BUILD_ARGS install $ADAPT_TO_SETUPTOOLS
|
||||
|
||||
if [ -e install_headers.py ]; then
|
||||
python install_headers.py $VIRTUAL_ENV
|
||||
fi
|
||||
|
||||
popd >/dev/null
|
||||
}
|
||||
|
||||
@@ -48,6 +54,8 @@ if [ -e "$ROOT/pygame_sdl2" ]; then
|
||||
setup "$ROOT/pygame_sdl2/"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ -e "$ROOT/cubism" ]; then
|
||||
export CUBISM="$ROOT/cubism"
|
||||
export CUBISM_PLATFORM=${CUBISM_PLATFORM:-linux/x86_64}
|
||||
|
||||
+1
-2
@@ -2,5 +2,4 @@
|
||||
|
||||
cd $(dirname $(dirname $(readlink -f $0)))
|
||||
|
||||
nice lib/py2-linux-x86_64/python add.py --real "$@"
|
||||
nice lib/py3-linux-x86_64/python add.py --real "$@"
|
||||
lib/py3-linux-x86_64/python add.py --real "$@"
|
||||
|
||||
@@ -16,7 +16,5 @@ sleep 2
|
||||
|
||||
rpy pull
|
||||
rpy build --python 3
|
||||
rpy build --python 2
|
||||
|
||||
lib/py3-linux-x86_64/python distribute.py
|
||||
lib/py2-linux-x86_64/python distribute.py
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
This searches Ren'Py for places where an inner function calls itself recursively.
|
||||
When that happens, the function winds up as part of a reference cycle, and has to be
|
||||
garbage collected manually.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import pathlib
|
||||
|
||||
current_path : pathlib.Path = pathlib.Path()
|
||||
results : set[tuple[str, str]] = set()
|
||||
|
||||
class FindRecursiveInnerFunction(ast.NodeVisitor):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def visit_Name(self, node):
|
||||
if node.id == self.name:
|
||||
results.add((current_path, self.name))
|
||||
|
||||
class FindInnerFunction(ast.NodeVisitor):
|
||||
def visit_FunctionDef(self, node):
|
||||
for i in node.body:
|
||||
FindRecursiveInnerFunction(node.name).visit(i)
|
||||
|
||||
|
||||
class FindFunctions(ast.NodeVisitor):
|
||||
def visit_FunctionDef(self, node):
|
||||
for i in node.body:
|
||||
FindInnerFunction().visit(i)
|
||||
|
||||
def main():
|
||||
global current_path
|
||||
|
||||
root = pathlib.Path(__file__).parent.parent
|
||||
|
||||
for fn in root.glob("renpy/**/*.py"):
|
||||
|
||||
current_path = fn
|
||||
|
||||
with open(fn) as f:
|
||||
tree = ast.parse(f.read())
|
||||
FindFunctions().visit(tree)
|
||||
|
||||
for fn, name in sorted(results):
|
||||
print(f"{fn}: {name}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -4,10 +4,40 @@ Changelog (Ren'Py 7.x-)
|
||||
|
||||
*There is also a list of* :doc:`incompatible changes <incompatible>`
|
||||
|
||||
|
||||
.. _renpy-8.4.0:
|
||||
|
||||
8.4.0
|
||||
=====
|
||||
|
||||
Requirement and Dependency Changes
|
||||
----------------------------------
|
||||
|
||||
Ren'Py now requires Windows 10 or later to run. This means that it will no longer run on Windows 7, 8, or 8.1.
|
||||
|
||||
Ren'Py is no longer built for 32-bit ARM linux. This drops support for the Raspberry Pi 3, and very old Chromebooks.
|
||||
Ren'Py is still being built for 32-bit ARM Android.
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
|
||||
The ``show expression`` statement has changed so that ``show expression "bg washington"`` is equivalent
|
||||
to ``show bg washington``. Previously, the expression would be used as a tag, which would rarely be correct.
|
||||
If a displayable is given instead of a string, a tag will be generated.
|
||||
|
||||
One the web platform, :var:`renpy.emscripten` is the emscripten module, making it available
|
||||
without needing to import it. You should still check that :var:`renpy.emscripten` is true before using it.
|
||||
|
||||
When :var:`config.nearest_neighbor` is true, image fonts are scaled using nearest neighbor scaling,
|
||||
rather than the default bilinear scaling.
|
||||
|
||||
The "Image Attributes" screen also indicates if transforms are applied to a layer, as it can be hard
|
||||
to determine otherwise.
|
||||
|
||||
|
||||
.. _renpy-8.3.3:
|
||||
.. _renpy-7.8.3:
|
||||
|
||||
|
||||
Fixes
|
||||
-----
|
||||
|
||||
@@ -27,15 +57,16 @@ used, requiring the player to explicitly request the Steam Deck keyboard when re
|
||||
|
||||
Synchronized start of audio/video no longer occurs on movie channels unless explicitly requested.
|
||||
|
||||
Whe rolling back to a point where a looping :class:`Movie` was showing, the looping movie will be played again
|
||||
When rolling back to a point where a looping :class:`Movie` was showing, the looping movie will be played again
|
||||
if it had been stopped.
|
||||
|
||||
When :func:`renpy.set_audio_filter` is called with `immediate` false, the filter will be applied when
|
||||
the queued file is played, rather than at some indeterminate time in the future.
|
||||
|
||||
When using text shaders to display text with outlines, Ren'Py will create psuedo-glyphs. These psuedo-glyphs
|
||||
When using text shaders to display text with outlines, Ren'Py will create pseudo-glyphs. These pseudo-glyphs
|
||||
cover the start and end of each line, and are used to ensure the outlines will be shown.
|
||||
|
||||
|
||||
.. _renpy-8.3.2:
|
||||
.. _renpy-7.8.2:
|
||||
|
||||
|
||||
@@ -149,3 +149,34 @@ Debug Functions
|
||||
---------------
|
||||
|
||||
.. include:: inc/debug
|
||||
|
||||
|
||||
Launcher Customization
|
||||
----------------------
|
||||
|
||||
It's possible to customize the Ren'Py launcher to select the files and directories
|
||||
that are available to click on. To do this, create project.json with the lines below::
|
||||
|
||||
{
|
||||
"renpy_launcher":
|
||||
{
|
||||
"open_directory":
|
||||
{
|
||||
"game": "game",
|
||||
"base": ".",
|
||||
"images": "game/images",
|
||||
"audio": "game/audio",
|
||||
"gui": "game/gui"
|
||||
},
|
||||
"edit_file":
|
||||
{
|
||||
"script.rpy": "game/script.rpy",
|
||||
"options.rpy": "game/options.rpy",
|
||||
"gui.rpy": "game/gui.rpy",
|
||||
"screens.rpy": "game/screens.rpy"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
If the file already exists, you'll want to edit in the renpy_launcher key and the lines below it.
|
||||
You can then edit the dictionaries to change the available files and directories.
|
||||
|
||||
@@ -12,12 +12,23 @@ features.
|
||||
Incompatible changes to the GUI are documented at :ref:`gui-changes`, as
|
||||
such changes only take effect when the GUI is regenerated.
|
||||
|
||||
Pending Deprecations
|
||||
--------------------
|
||||
.. _incompatible-8.4.0:
|
||||
|
||||
Ren'Py 7.8 is the last release to support Python 2.
|
||||
8.4.0
|
||||
-----
|
||||
|
||||
Ren'Py 8.4 will drop support for the original OpenGL renderer (gl1), and for Windows 7, 8, and 8.1.
|
||||
**Show expression.** The ``show expression`` statement has been changed so that::
|
||||
|
||||
show expression "bg washington"
|
||||
|
||||
is exactly equivalent to:
|
||||
|
||||
show bg washington
|
||||
|
||||
Previously, this would use the expression itself as the tag. When the expression is not a string,
|
||||
a unique tag is created for the show expression statement. This change can be reverted with::
|
||||
|
||||
define config.old_show_expression = True
|
||||
|
||||
|
||||
.. _incompatible-8.3.0:
|
||||
|
||||
@@ -64,7 +64,9 @@ it's running on.
|
||||
|
||||
.. var:: renpy.emscripten
|
||||
|
||||
Has a true value when running in the browser.
|
||||
Has a true value when running in the browser. That value is specifically the
|
||||
emscripten module that lets you interact with the browser, see :func:`renpy.emscripten.run_script`
|
||||
and similar functions.
|
||||
|
||||
.. var:: renpy.mobile
|
||||
|
||||
|
||||
+500
-497
File diff suppressed because it is too large
Load Diff
+7
-22
@@ -226,19 +226,20 @@ Javascript
|
||||
----------
|
||||
|
||||
Ren'Py can run Javascript, using three functions in the ``emscripten``
|
||||
module. This module is only present when running inside the web browser,
|
||||
which can be tested using :var:`renpy.emscripten` - though you still need
|
||||
to import the module before using it.
|
||||
module. This module is available as :var:`renpy.emscripten` when
|
||||
running on the web platform. When not running on the web platform,
|
||||
renpy.emscripten is False.
|
||||
|
||||
.. function:: emscripten.run_script(script)
|
||||
|
||||
.. function:: renpy.emscripten.run_script(script)
|
||||
|
||||
Runs the given Javascript script. This does not return a result.
|
||||
|
||||
.. function:: emscripten.run_script_int(script)
|
||||
.. function:: renpy.emscripten.run_script_int(script)
|
||||
|
||||
Runs the given Javascript script, and returns its result as an integer.
|
||||
|
||||
.. function:: emscripten.run_script_string(script)
|
||||
.. function:: renpy.emscripten.run_script_string(script)
|
||||
|
||||
Runs the given Javascript script, and returns its result as a string.
|
||||
|
||||
@@ -251,22 +252,6 @@ and window.renpy_set functions. For the documentation of these functions, please
|
||||
read ``web/renpy-pre.js``.
|
||||
|
||||
|
||||
Bytecode Cache
|
||||
--------------
|
||||
|
||||
In Ren'Py 8.1, loading may take longer than required, as Ren'Py may need
|
||||
to compile the python into bytecode after downloading it. To avoid this, retrieve
|
||||
the bytecode cache by:
|
||||
|
||||
* Opening the game in the browser. Hit f12 while the presplash is loading
|
||||
to open the javascript console.
|
||||
|
||||
* In the javascript console, type ``downloadBytecode()``. This will cause
|
||||
your browser to download ``bytecode-311.rpyb``.
|
||||
|
||||
* Place this in the ``game/cache`` directory of your game, and rebuild.
|
||||
|
||||
|
||||
Hamburger Menu
|
||||
--------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user