핫 스왑 가능한 C ++ 모듈을 어떻게 구현할 수 있습니까?


39

빠른 반복 시간은 게임을 개발하는 데 중요합니다. 제 생각에는 멋진 그래픽과 엔진보다 훨씬 많은 기능이 있습니다. 많은 소규모 개발자가 스크립팅 언어를 선택하는 것은 놀라운 일이 아닙니다.

게임을 일시 중지하고 자산 및 코드를 수정 한 다음 계속해서 변경 사항을 즉시 적용 할 수있는 Unity 3D 방식은이 기능에 절대적으로 좋습니다. 내 질문은 C ++ 게임 엔진에서 비슷한 시스템을 구현 한 사람이 있습니까?

나는 정말 최고급 엔진이 있다고 들었지만, 자체 개발 한 엔진이나 게임에서 그것을 할 수있는 방법이 있는지 알아내는 데 더 관심이 있습니다.

분명히 트레이드 오프가있을 것입니다. 게임이 일시 중지 된 동안 코드를 다시 컴파일하고 모든 상황이나 모든 플랫폼에서 다시로드하고 작동시킬 수 있다고 상상할 수 없습니다.

그러나 아마도 AI 프로그래밍과 간단한 레벨 로직 모듈이 가능할 것입니다. 단기 (또는 장기) 프로젝트에서 그렇게하고 싶지는 않지만 궁금합니다.

답변:


26

일반적인 C ++ 코드로는 그렇지 않지만 확실히 할 수 있습니다. 런타임에 동적으로 링크하고 다시로드 할 수있는 C 스타일 라이브러리를 작성해야합니다. 이를 가능하게하려면 라이브러리는 다시로드 할 때 라이브러리에 제공 할 수있는 불투명 포인터 내에 모든 상태 를 포함해야합니다 .

Timothy Farrar는 자신의 접근 방식에 대해 설명합니다.

"개발을 위해 코드는 라이브러리로 컴파일되며 소형 로더 프로그램은 라이브러리의 사본을 만들고 프로그램을 실행하기 위해 라이브러리 사본을로드합니다. 프로그램은 시작시 모든 데이터를 할당하고 하나의 단일 포인터를 사용하여 모든 데이터를 참조합니다. 읽기 전용 데이터 이외의 전역은 사용하지 않습니다. 프로그램이 실행되는 동안 원래 라이브러리를 다시 컴파일 할 수 있습니다. 한 번의 키 누름으로 프로그램이 로더로 돌아가고 단일 데이터 포인터를 반환합니다. 로더는 새 라이브러리의 사본을 만듭니다. 사본을로드하고 데이터 포인터를 새 코드로 전달한 다음 엔진이 중단 된 지점에서 계속 진행합니다. " ( 원본 출처 , 이제 웹 아카이브를 통해 사용 가능 )


나는 당신이 실제로 질문에 대답하기 때문에 이것을 Blair의 대답보다 선호합니다.
Jonathan Dickinson

15

핫 스와핑은 이진 코드로 해결하기가 매우 어려운 문제입니다. 코드가 별도의 동적 링크 라이브러리에 배치 된 경우에도 코드는 함수의 주소가 재 컴파일로 변경 될 수 있으므로 메모리의 함수에 대한 직접 참조가 없는지 확인해야합니다. 이것은 본질적으로 가상 함수를 사용하지 않음을 의미하며 함수 포인터를 사용하는 것은 일종의 디스패치 테이블을 통해 그렇게합니다.

털이 제한적인 물건. 빠른 반복이 필요한 코드에는 스크립팅 언어를 사용하는 것이 좋습니다.

현재 우리의 코드베이스는 C ++과 Lua의 혼합입니다. Lua는 우리 프로젝트의 작은 부분도 아니며 거의 50/50 분할입니다. 우리는 Lua의 즉각적인 리로딩을 구현하여 코드 라인을 변경하고 다시로드하고 계속 진행할 수 있습니다. 실제로 게임을 다시 시작하지 않고도 Lua 코드에서 발생하는 충돌 버그를 해결하기 위해이 작업을 수행 할 수 있습니다!


3
또 다른 중요한 점은 시스템을 스크립트로 유지하지 않더라도 초기에 많은 반복을 원할 경우 Lua에서 시작한 다음 C ++로 이동하는 것이 여전히 합리적 일 수 있다는 것입니다. 추가 성능과 반복 속도가 느려졌습니다. 여기서 명백한 단점은 코드 포팅을 끝내는 데 많은 시간이 걸리지 만 로직을 올바르게 얻는 것은 어려운 부분입니다.
Logan Kincaid

15

( 유머러스 한 정신적 이미지 외에 다른 것이 없다면 "원숭이 패치"또는 "덕 펀치" 라는 용어를 알고 싶을 수도 있습니다 .)

이를 제외하고 : 목표가 "동작"변경에 대한 반복 시간을 줄이는 경우 대부분의 방식으로 접근 할 수있는 몇 가지 접근 방식을 시도해보고 앞으로 더 많은 기능을 구현할 수 있습니다.

(이것은 약간의 접선에서 나올 것이지만, 나는 그것이 돌아올 것이라고 약속합니다!)

  • data로 시작하여 작게 시작합니다. 경계에서 다시로드 ( "레벨"등) 한 다음 OS 기능을 사용하여 파일 변경 알림을 받거나 정기적으로 폴링 합니다.
  • (보너스 포인트 및로드 시간 단축 (다시 반복 시간 감소)은 데이터 베이킹을 참조하십시오 .)
  • 스크립트는 data 이며 동작을 반복 할 수 있습니다. 스크립팅 언어를 사용하는 경우 이제 해석 또는 컴파일 된 스크립트를 다시로드 할 수있는 알림 / 기능이 있습니다. 런타임 유연성을 높이기 위해 인터프리터를 게임 내 콘솔, 네트워크 소켓 등에 연결할 수도 있습니다.
  • 코드도 데이터 일 수 있습니다 . 컴파일러는 오버레이 , 공유 라이브러리, DLL 등을 지원할 수 있습니다 . 이제 수동 또는 자동으로 오버레이 또는 DLL을 언로드 및 재로드하는 "안전한"시간을 선택할 수 있습니다. 다른 답변은 여기에 자세히 설명되어 있습니다. 이것의 일부 변형은 암호화 서명 vecification, NX (no-execute) 비트 또는 유사한 보안 메커니즘을 망칠 수 있습니다 .
  • 깊은 버전의 저장 /로드 시스템을 고려하십시오 . 코드 변경에도 불구하고 상태를 강력하게 저장하고 복원 할 수 있다면 게임을 종료하고 정확히 같은 시점에 새로운 논리로 다시 시작할 수 있습니다. 말보다 쉽지만, 가능하지만 명령을 변경하기 위해 메모리를 파는 것보다 훨씬 쉽고 이식성이 뛰어납니다.
  • 게임의 구조와 결정에 따라 기록 및 재생 이 가능할 수 있습니다 . 해당 레코딩이 "게임 명령"(예 : 카드 게임을 생각하는 것) 위에있는 경우 원하는 모든 렌더링 코드를 변경하고 레코딩을 재생하여 변경 사항을 볼 수 있습니다. 일부 게임의 경우 시작 매개 변수 (예 : 임의의 시드)를 기록한 다음 사용자 작업을 기록하는 것만큼이나 "쉽습니다". 어떤 경우에는 훨씬 더 복잡합니다.
  • 컴파일 시간줄이기 위해 노력하십시오 . 위에서 언급 한 저장 /로드 또는 레코드 / 재생 시스템 또는 오버레이 또는 DLL과 함께 사용하면 다른 것보다 처리 시간이 줄어들 수 있습니다.

이러한 점 중 많은 부분은 데이터 나 코드를 다시로드하지 않아도 도움이됩니다.

일화 지원 :

대규모 PC RTS (~ 120 명 팀, 주로 C ++)에는 적어도 세 가지 목적으로 사용되는 매우 깊은 상태 절약 시스템이있었습니다.

  • 멀티 플레이어 게임이 10-30 프레임마다 하나의 CRC를 잠금 단계 시뮬레이션으로 유지하기 위해 "얕은"저장이 디스크가 아닌 CRC 엔진에 공급되었습니다. 이것은 아무도 속이지 않았 음을 확인하고 몇 프레임 후에 비동기 버그를 잡았습니다.
  • 멀티 플레이어 비동기 버그가 발생하면 매 프레임마다 추가 저장이 수행되고 CRC 엔진에 다시 공급되지만 이번에는 CRC 엔진이 더 작은 바이트 배치에 대해 많은 CRC를 생성합니다. 이러한 방식으로, 마지막 프레임 내에서 어느 부분의 상태가 분기되기 시작했는지 정확하게 알 수 있습니다. 우리는 이것을 사용하여 AMD와 인텔 프로세서 사이에 "기본 부동 소수점 모드"라는 큰 차이를 발견했습니다.
  • 일반적인 깊이 저장은 예를 들어 유닛이 재생 한 정확한 애니메이션 프레임을 저장하지 않을 수 있지만, 모든 유닛의 위치, 체력 등을 가져 와서 게임 플레이 중 언제든지 저장하고 다시 시작할 수 있습니다.

그 후 DS의 C ++ 및 Lua 카드 게임에서 결정적인 레코드 / 재생을 사용했습니다. 우리는 AI (C ++ 측)를 위해 설계된 API에 연결하고 모든 사용자 및 AI 작업을 기록했습니다. 이 기능은 게임에서 (플레이어에게 재생을 제공하기 위해) 사용했지만 문제를 진단하는 데에도 사용되었습니다. 충돌이나 이상한 동작이있을 때 저장 파일을 가져와 디버그 빌드에서 재생하기 만하면됩니다.

또한 오버레이를 몇 번 이상 사용했으며,이 디렉토리를 "이 디렉토리를 자동으로 스파이더 링하고 핸드 헬드에 새 컨텐츠를 업로드"시스템과 결합했습니다. 컷씬 / 레벨 / 무엇이든 그대로두고 다시 돌아 오는 것만으로도 새로운 데이터 (스프라이트, 레벨 레이아웃 등)뿐만 아니라 오버레이에 새로운 코드도로드됩니다. 불행히도 코드를 특수하게 처리하는 복제 방지 및 해킹 방지 메커니즘으로 인해 최신 핸드 헬드에서는 훨씬 어려워지고 있습니다. 우리는 여전히 루아 스크립트를 위해 그것을합니다.

마지막으로, 명령 opcode를 직접 패치하여 약간의 오리 펀치를 할 수 있습니다. 이것은 고정 플랫폼과 컴파일러를 사용하는 경우 가장 효과적이며, 유지 관리가 거의 불가능하고 버그가 발생하기 쉽고 빠르게 달성 할 수있는 작업이 제한되어 있기 때문에 디버깅하는 동안 코드를 다시 라우팅하는 데만 사용됩니다. 그것은 수행 하지만, 당신에게 서둘러 당신의 명령 세트 아키텍처에 대한 많은의 지옥을 가르칩니다.


6

런타임시 모듈을 동적 링크 라이브러리 (또는 UNIX의 공유 라이브러리)로 구현하고 dlopen () 및 dlsym ()을 사용하여 라이브러리에서 함수를 동적으로로드하는 핫스왑 모듈과 같은 작업을 수행 할 수 있습니다.

Windows의 경우 동등한 것은 LoadLibrary 및 GetProcAddress입니다.

이것은 C 메소드이며 C ++에서 사용하여 함정이 있습니다 . 여기에서 읽을 수 있습니다 .


이 안내서는 핫 스왑 가능 라이브러리를 만드는 열쇠입니다. 감사합니다
JqueryToAddNumbers

4

Visual Studio C ++를 사용하는 경우 특정 상황에서 실제로 코드를 일시 중지하고 다시 컴파일 할 수 있습니다. Visual Studio는 편집 및 계속을 지원합니다 . 디버거에서 게임에 연결하고 중단 점에서 중지 한 다음 중단 점 다음에 코드를 수정하십시오. 저장하고 계속하려면 Visual Studio에서 코드를 다시 컴파일하고 실행중인 실행 파일에 다시 삽입 한 다음 계속합니다. 모든 것이 잘 진행되면 전체 컴파일 빌드 테스트 사이클을 수행하지 않고도 실행중인 게임에 변경 사항이 적용됩니다. 그러나 다음은이 작업을 중지합니다.

  1. 콜백 기능을 변경하면 제대로 작동하지 않습니다. E & C는 새로운 코드를 추가하고 새로운 블록을 가리 키도록 콜 테이블을 변경함으로써 작동합니다. 콜백 및 함수 포인터를 사용하는 모든 경우 여전히 수정되지 않은 이전 호출을 실행합니다. 이 문제를 해결하려면 정적 함수를 호출하는 래퍼 콜백 함수를 사용할 수 있습니다
  2. 헤더 파일 수정은 거의 작동하지 않습니다. 실제 함수 호출을 수정하기 위해 설계되었습니다.
  3. 다양한 언어 구성으로 인해 신비하게 실패합니다. 내 개인적인 경험에서 열거 형과 같은 것을 미리 선언하면 종종 그렇게 할 것입니다.

UI 반복과 같은 작업의 속도를 크게 향상시키기 위해 편집 및 계속을 사용했습니다. 실수로 두 상자의 그리기 순서를 바꾸어 아무것도 볼 수 없다는 점을 제외하고 코드에 완전히 내장 된 작동하는 UI가 있다고 가정 해 봅시다. 2 줄의 코드 라이브를 변경하면 20 분의 컴파일 / 빌드 / 테스트주기를 절약하여 사소한 UI 수정을 확인할 수 있습니다.

이것은 프로덕션 환경을위한 완벽한 솔루션이 아니며 가능한 많은 로직을 데이터 파일로 이동 한 다음 해당 데이터 파일을 다시로드 할 수있는 최상의 솔루션을 찾았습니다.


어떤 컴파일주기가 20 분이 걸립니까?!? 차라리 차라리
JqueryToAddNumbers

3

다른 사람들이 말했듯이 C ++을 동적으로 연결하는 것은 어려운 문제입니다. 그러나 이것은 해결 된 문제입니다. COM 또는 수년 동안 적용되었던 마케팅 이름 중 하나 인 ActiveX에 대해 들어 보셨을 것입니다.

COM은 개발자의 관점에서 다소 나쁜 이름을 가지고 있습니다. C ++ 구성 요소를 사용하여 기능을 제공하는 C ++ 구성 요소를 구현하는 데 많은 노력을 기울일 수 있기 때문입니다 (ATL-ActiveX 템플릿 라이브러리를 사용하면 더 쉬워집니다). 예를 들어, Word 스프레드 시트에 Excel 스프레드 시트를 포함 시키거나 Excel 스프레드 시트에 Visio 다이어그램을 포함하는 등의 응용 프로그램을 사용하는 응용 프로그램은 하루에 약간의 충돌을 일으키기 때문에 소비자 입장에서는 이름이 잘못되었습니다. 그리고 이는 동일한 문제로 귀결됩니다. Microsoft가 제공하는 모든 지침에도 불구하고 COM / ActiveX / OLE는 제대로 이해하기가 어렵습니다.

COM의 기술 자체가 본질적으로 나쁘지는 않다는 점을 강조하겠습니다. 우선 DirectX는 COM 인터페이스를 사용하여 기능을 노출하고 ActiveX 컨트롤을 사용하여 Internet Explorer를 포함하는 여러 응용 프로그램과 마찬가지로 충분히 작동합니다. 둘째, C ++ 코드를 동적으로 연결하는 가장 간단한 방법 중 하나입니다. COM 인터페이스는 본질적으로 순수한 가상 클래스입니다. CORBA와 같은 IDL이 있지만 특히 정의한 인터페이스가 프로젝트 내에서만 사용되는 경우이를 사용하지 않아도됩니다.

Windows 용으로 작성하지 않는 경우 COM을 고려할 가치가 없다고 생각하지 마십시오. Mozilla는 C ++ 코드를 구성하는 방법이 필요했기 때문에 Firefox 브라우저에서 사용되는 코드베이스에서이를 다시 구현했습니다.


2

게임 플레이 코드를위한 런타임 컴파일 된 C ++ 구현이 있습니다 . 또한 동일한 게임 엔진이 하나 이상 있다는 것을 알고 있습니다. 까다 롭지 만 실행 가능합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.