스크린 언어 최적화

Ren'Py는 스크린 언어의 속도를 최적화하는 여러 기법을 사용합니다. 시뮬레이션 게임처럼 복잡한 인터페이스를 만들 때 스크린 언어의 작동 방식을 이해하면 성능을 최대한 끌어내는 데 도움이 됩니다.

이 안내서는 Ren'Py 6.18에서 추가한 두 번째 스크린 언어 구현에 적용됩니다. Ren'Py 6.17 이전에 만든 게임이라면 런처에서 강제 재컴파일(Force Recompile)을 선택해야 스크린이 최신 버전으로 갱신될 수 있습니다.

이 안내서가 올바른 프로그래밍 습관을 대신하지는 않습니다. 중첩 반복문으로 불필요한 작업을 많이 하는 스크린은 그런 반복을 피하는 스크린보다 느립니다. 여기서 설명하는 기법을 이해하는 것도 중요하지만, Ren'Py가 작업을 최적화하게 하는 것보다 작업 자체를 없애는 편이 항상 낫습니다.

매개변수 목록

최상의 성능을 위해 모든 스크린에 매개변수 목록을 정의해야 합니다. 매개변수를 받지 않으면 빈 목록을 정의하세요. 다음 스크린은

screen test():
    vbox:
        for i in range(10):
            text "[i]"

아래 스크린보다 빠릅니다.

screen test:
    vbox:
        for i in range(10):
            text "[i]"

매개변수 목록 없이 정의한 스크린은 표시할 때 내부에서 사용하는 모든 이름을 재정의할 수 있습니다. 따라서 Ren'Py가 스크린을 더 보수적으로 분석해야 하며, 가능한 최적화가 제한될 수 있습니다.

예측 로딩

스크린을 미리 예측 로딩하면 성능이 좋아집니다. Ren'Py가 예측 시점에 스크린을 실행하고 사용되는 이미지를 미리 불러오기 때문입니다.

Ren'Py는 두 가지 방식으로 스크린을 자동 예측합니다.

  • show screen과 call screen 문으로 표시할 스크린을 예측합니다.

  • Show()와 ShowMenu() 액션으로 표시할 스크린을 예측합니다.

Python에서 스크린을 표시한다면 표시하기 전에 예측을 시작하는 것이 좋습니다. 시작하려면 renpy.start_predict_screen(), 중지하려면 renpy.stop_predict_screen() 함수를 사용합니다.

디스플레이어블 재사용

디스플레이어블을 만드는 스크린 언어 문을 평가할 때 Ren'Py는 위치 인수와 속성이 그 문을 마지막으로 평가했을 때와 같은지 확인합니다. 같으면 새로 만들지 않고 기존 디스플레이어블을 재사용합니다.

재사용에는 여러 성능상 이점이 있습니다. 새 디스플레이어블을 만드는 비용을 줄이며, 내부 상태가 많은 디스플레이어블에서는 효과가 클 수 있습니다. 더욱이 대부분의 경우 표시 전에 다시 렌더링할 필요가 없어져 속도가 크게 향상될 수 있습니다.

Ren'Py는 Python의 == 연산자로 표현되는 동등성을 사용해 위치 인수와 속성을 비교합니다. 이 개념은 액션에도 적용됩니다. 어느 액션을 실행하든, 활성 또는 선택 상태를 조회하든 결과에 차이가 없어 서로 구별할 수 없는 두 액션은 동등하다고 봅니다.

Ren'Py에서 제공하는 모든 액션은 이 정의를 따릅니다. 직접 액션을 정의할 때도 적절한 __eq__ 메서드를 제공해 이러한 동등성을 지원하는 것이 좋습니다. 예를 들면 다음과 같습니다.

class TargetShip(Action):
    def __init__(self, ship):
        self.ship = ship

    def __eq__(self, other):
        if not isinstance(other, TargetShip):
            return False

        return self.ship is other.ship

    def __call__(self):
        global target
        target = self.ship

__eq__ 함수는 모든 필드를 비교하고, 동등성(==)과 동일성(is) 비교를 알맞게 사용하도록 주의해서 정의해야 합니다.

상수 표현식과 순수 함수

Ren'Py는 상수 변수와 순수 함수의 성질로 스크린 평가 속도를 높이고 스크린 일부의 평가를 완전히 생략할 수 있습니다.

정의

평가할 때 항상 같은 값을 나타내는 표현식을 상수(const)라고 합니다. Ren'Py에서 표현식이 상수이기 위한 필요충분조건은 다음 표현식들이 항상 같은 상수 값으로 평가되거나 정의되지 않는 것입니다.

  • 다른 피연산자도 상수일 때, 해당 표현식에 임의의 단항, 이항, 삼항 연산자를 적용한 표현식.

  • 해당 표현식의 필드에 접근한 표현식.

  • 숫자나 객체로 해당 표현식을 인덱싱한 표현식.

Python 숫자와 문자열은 상수이며, 모든 요소가 상수인 list, tuple, set, dict 리터럴도 상수입니다. Ren'Py는 define 문으로 정의한 변수를 상수로 표시합니다. renpy.const()와 renpy.not_const()로 무엇을 상수로 취급할지 추가 제어할 수 있습니다. 기본 상수 이름 목록은 아래 상수 이름 절에 있습니다.

절대 바뀌지 않는 변수는 define으로 정의하면서 상수로 선언하는 것이 좋습니다. 예를 들면 다음과 같습니다.

define GRID_WIDTH = 20
define GRID_HEIGHT = 10

호출 가능한 함수, 클래스, 액션이 모든 인수가 상수일 때 항상 같은 상수 값을 반환하면 순수(pure)하다고 합니다. 따라서 순수 함수를 상수 표현식으로 호출한 표현식도 상수 표현식입니다.

많은 기본 함수, 클래스, 액션이 순수한 것으로 표시되어 있습니다. 목록은 아래 순수 이름 절에 있습니다.

함수를 순수하다고 선언하려면 renpy.pure()를 사용합니다. 기본 스토어에서 선언한 함수의 데코레이터로 사용할 수 있습니다.

상수 표현식과 순수 함수는 다음 이벤트 전후로 같은 값을 유지할 필요가 없습니다.

  • 초기화 단계 종료.

  • 언어 변경.

  • 스타일 재구성.

상수가 스크린 언어를 최적화하는 방식

스크린 언어의 인수와 속성을 상수로 만들면 세 가지 이점이 있습니다.

첫째, 상수 인수와 속성은 초기화 단계 종료, 언어 변경, 스타일 재구성 시 스크린을 준비하면서 평가합니다. 이후에는 다시 평가하는 데 시간을 쓰지 않아도 됩니다.

둘째, 상수는 디스플레이어블 재사용과 잘 맞습니다. 디스플레이어블의 모든 인수와 속성이 상수이면 항상 재사용할 수 있어 재사용의 모든 이점을 얻습니다.

마지막으로 Ren'Py는 모든 인수와 속성, 제어 흐름에 영향을 주는 표현식이 상수인 디스플레이어블 트리를 만나면 표현식을 평가하거나 디스플레이어블을 만들지 않고 트리 전체를 재사용합니다. 성능이 크게 향상될 수 있습니다.

예를 들어 다음 스크린은 처음 예측되거나 표시된 이후에는 Python 코드를 실행하거나 디스플레이어블을 생성하지 않습니다.

screen mood_picker():
    hbox:
        xalign 1.0
        yalign 0.0

        textbutton "기쁨" action SetVariable("mood", "happy")
        textbutton "슬픔" action SetVariable("mood", "sad")
        textbutton "화남" action SetVariable("mood", "angry")

상수 텍스트

텍스트를 정의할 때 새 방식의 텍스트 치환을 포함하는 문자열은 상수라는 점에 유의하세요.

$ t = "안녕, 세상아."
text "[t]"

텍스트가 담긴 변수를 직접 전달하면 일반적으로 상수가 아닙니다.

$ t = "안녕, 세상아."
text t

퍼센트 치환을 사용한 경우도 상수가 아닙니다.

$ t = "안녕, 세상아."
text "%s" % t

마지막으로 _ 텍스트 번역 함수는 순수 함수이므로 문자열을 전달하면 표현식 전체가 상수입니다.

text _("획득한 점수: [score]")

텍스트를 담은 변수에 치환이 포함되어 있다면 !i 변환 플래그를 사용해야 합니다.

$ who = "제인"
$ t = "안녕, [who]!"
text '그러고 나서 나는 그녀에게 "[t!i]"라고 말했다.'

상수 관련 함수

renpy.const(name)

스토어의 변수를 상수로 선언합니다.

변수의 값이나 인덱싱 및 속성 접근으로 도달하는 어떤 값도 바뀌지 않으면 상수입니다. define, init, translate python 블록 밖에서는 상수로 유지되어야 합니다.

name

상수로 선언할 변수 이름 문자열입니다.

renpy.not_const(name)

스토어의 이름을 상수가 아닌 것으로 선언합니다.

renpy.const()와 renpy.pure() 호출의 효과를 취소합니다.

name

상수가 아닌 것으로 선언할 이름입니다.

renpy.pure(fn)

함수를 순수 함수로 선언합니다. define, init, translate python 블록 밖에서 같은 인수로 호출하면 항상 같은 값을 반환해야 합니다.

fn

순수 함수로 선언할 함수의 이름입니다. 이름 문자열이나 함수 자체를 지정할 수 있습니다. 함수가 모듈 안에 있고 문자열을 전달한다면 점으로 구분한 모듈 이름도 포함해야 합니다.

fn을 반환하므로 데코레이터로 사용할 수 있습니다.

프로파일링

Ren'Py는 renpy.profile_screen 함수로 스크린 실행의 프로파일링을 지원합니다.

class renpy.profile_screen(name, predict=False, show=False, update=False, request=False, time=False, debug=False, const=False)

문자열이어야 하는 name이라는 스크린의 프로파일링을 요청합니다.

name을 제외한 모든 인수는 키워드로 전달해야 합니다. 인수는 세 그룹으로 나뉩니다.

첫 번째 그룹은 프로파일링 시점을 결정합니다.

predict

참이면 스크린을 예측할 때 프로파일링합니다.

show

참이면 스크린을 처음 표시할 때 프로파일링합니다.

update

참이면 스크린을 갱신할 때 프로파일링합니다.

request

참이면 F8을 눌러 요청할 때 프로파일링합니다.

두 번째 그룹은 프로파일링 시 생성할 출력을 제어합니다.

time

참이면 스크린 평가에 걸린 시간을 기록합니다.

debug

참이면 다음과 같은 스크린 평가 정보를 기록합니다.

  • Ren'Py가 상수로 판단한 디스플레이어블.

  • 평가가 필요했던 인수.

  • 재사용한 디스플레이어블.

이 디버그 정보를 생성하고 저장하는 데 상당한 시간이 걸립니다. 따라서 debug를 설정했다면 time 출력은 신뢰할 수 없습니다.

마지막 그룹은 Ren'Py 실행당 한 번 생성할 출력을 제어합니다.

const

스크린에서 상수 및 비상수로 표시된 변수를 보여 줍니다.

모든 프로파일링 출력은 game 디렉터리의 profile_screen.txt에 기록합니다.

상수 이름

다음 이름은 기본적으로 상수입니다.

  • False

  • None

  • True

  • config

  • style

순수 이름

다음 이름은 기본적으로 순수하면서 상수입니다.