Minecraft screenshot with moon setting in y=-59
개발
기타 개발
block-pixel 개발 후기
2022-06-09, 19:21 | HoonKun
block-pixel 개발 후기

마인크래프트 맵의 일부 데이터를 git을 통해 실시간 버전관리할 수 있는 플러그인 'block-pixel' 의 개발 후기.

서론

block-pixel 에 마지막으로 커밋을 남긴지 한 달이 조금 넘어가는 것 같다.
사실 누군가... 써보고 피드백을 남겨줄 그런 사람이 있었다면 더 개발을 했겠으나 지금으로써는 더 이상 개발할 여지가 없다고 판단해서, 더 기억이 흐려지기 전에 후기를 남기고자 한다.
**플러그인 사용자의 측면에서 플러그인에 대해 더 자세히 알고싶다면 여기를 참고해주세요.

block-pixel

이름은 별 생각 없이 즉흥적으로 지어서 별 의미가 없다.
이 block-pixel 이라는 친구는 마인크래프트의 서버 플러그인으로, git을 통해 마인크래프트의 지형, 플레이어를 제외한 엔티티 데이터를 실시간으로 버전관리할 수 있게 도와주는 플러그인이다.

개발 전 목표

개발에 본격적으로 들어가기 전, 지형/엔티티 데이터에 대해 '적어도 이거는 가능했으면 좋겠다' 싶었던 목표들은 아래와 같았다.

  • 커밋 / 리셋을 통해 백업을 만들고 특정 백업 지점 이후의 변경을 삭제하는 기능
  • 브랜치 / 체크아웃을 통해 여러 분기로 맵을 전개시킬 수 있도록 하는 기능
  • Git 의 알고리즘을 그대로 사용하여 여러 브랜치의 맵을 병합하는 기능
  • 위의 기능들이 전부 서버의 중지나 플레이어의 로그아웃 없이 플레이어가 온라인인 상태에서 실시간으로 진행될 것.

개발 중 마주친 벽

데이터 변환

초기에는, 월드 데이터를 human-readable 한 일정 형태로 변환하여, 그것을 버전관리하고자 했다.
왜냐하면 그렇지 않으면 병합 기능을 구현하기가 어려울거라 판단했기 때문이다.

그러나 그 방식은 개발 3일 째에 폐기하기로 결정했다.

우선, 마인크래프트 월드 데이터는 철저하게 압축이 잘 되어있다. byte 단위의 값 하나하나가 서로 다른 의미를 가지며, 그것 마저도 gzip, zlib 등의 알고리즘으로 다시 압축되어있다.
이걸 압축도 안하고, human-readable 한 형태로 바꿔버리면 이론적으로 하나의 값이 8배로 크기가 불어날 뿐더러 압축도 하지 않으므로 더 늘어난다.

거기에 변환한 데이터가 JSON이었는데 JSON의 타입 체계와 NBT의 타입 체계가 서로 달라 타입 변환에 실패한다는 문제가 있었고, 게다가 이걸 읽고 쓰는 속도도 감안해야 하며, 무엇보다 마인크래프트 내 모든 데이터를 파악하고 재정의해야한다는 문제가 있었다.

물론 human-readable할 경우 git으로 관리하면 변경사항만 관리되기 때문에 용량이 조금 절약되는 면이 있지만, 이미 초기 월드의 변환 결과물 용량이 200MB가 넘는 것을 확인한 뒤에는 그냥 바이너리 데이터를 버전관리하기로 결정했다.

월드 데이터 언로드

그리하여 JGit 라이브러리를 통해 Git 명령을 수행하기 전, 우선 월드 바이너리 데이터를 로컬 저장소로 복사했는데 그 월드 데이터가 깨져있었다(열리긴 하는데 이상하게 망가져있었다).
단순히 월드 파일을 복사했을 뿐인데, 다시 그 맵을 붙혀넣어 적용해보면 월드가 이상하게 깨져있었다.

서버를 멈추고 복사할 경우 제대로 복사되는 것을 확인하여 서버가 실행 중일때는 파일 IO를 닫지 않아 복사가 이상하게 된다고 판단했다.

서버가 IO를 닫게 하려면 월드를 언로드하면 된다는 자료를 확인했으나, 메인 월드는 플레이어 데이터 때문에 언로드가 어려웠다.
그리하여 버전관리가 진행되는, 기존 메인 월드와 완전히 동일하지만 플레이어 데이터 등이 없고 지형데이터만 있는 월드를 추가하고, 언로드를 위해 플레이어가 잠시 머물게 될 __void__ 월드를 추가하여 월드를 언로드할 수 있게 했다.

결론적으로, 월드 데이터가 깨지는 것을 막기 위해 서버가 파일 IO를 닫을 수 있도록, 메인 월드와 버전관리 월드를 나누고 플레이어가 잠시 머물게될 월드를 추가했다.
(더 자세한 설명은 여기에 서술되어있습니다)

이 과정으로 플레이어는 서버로부터 로그아웃하거나 서버를 켜고 끄지 않아도 커밋/리셋/브랜치/체크아웃을 수행할 수 있게 되었다.

병합

개발 초기에 데이터 변환을 포기하고 바이너리 데이터를 직접 관리하도록 했기 때문에, Git의 알고리즘을 사용하여 병합하는 것은 불가능했다.

그러나 병합 정도는 할 수 있으면 좋을 것 같아서, 직접 병합 알고리즘을 작성하기로 한다. 기본적으로 두 브랜치가 갈라진 기반 커밋, 병합하려는 두 커밋 사이에서 비교가 이루어져야 함을 확인했다.

광원

마인크래프트의 광원은 실시간 계산이 아닌 파일에 저장된다. 그런데 광원은 다른 광원의 빛값과 상호작용하여 특정 알고리즘에 의해 산출되는 값이라, 수동으로 계산하여 병합하기가 어려웠다.

다만, spigot 에서 API를 통해 광원을 부쉈다가 다시 설치할 경우 다른 종속 블럭 등에 영향을 주지 않고 광원만 정상적으로 업데이트 하는 것을 확인하여, 그렇게 진행했다.

속도

처음에는 단순히 모든 리젼, 모든 청크, 모든 블럭을 비교하도록 했다. 당연히 빠를리가 없다.
1.18.2 기준 한 청크에 16x16x319 개의 블럭이 있으며, 한 리젼에 16x16개의 청크가 있다. 하나의 월드는 생성 직후에도 4~9개의 리젼으로 시작하므로, 당연히 느렸다.

처음에는, 배열의 contentEquals나 오브젝트의 equals 도 어차피 순회하여 동일성판단을 할텐데 동일성 판단을 하면 괜히 두 번 반복하는게 아니냐 같은 생각을 했다.
그런데 그것은 틀린 생각이었다. data classequals를 잘 재정의하고 동일성을 판단하여 같으면 병합(비교) 로직을 건너뛰도록 했더니 병합 속도가 꽤 큰 폭으로(약 3분 > 30초) 향상되었다.

광원에 대한 병합 방식도, 모든 광원을 부쉈다가 다시 설치하다가 이후 변경된 광원만 다시 설치하도록 해서 병합 속도를 더 향상했다.

메모리

비록 데이터 변환 과정은 없지만, NBT 데이터를 그대로 kotlin 오브젝트로 변환하는 것 만으로도 메모리가 부족했다.
기존 방식은 전체 지형 데이터를 읽어 한 번에 병합하고 한 번에 쓰는 방식이었으나, 이것도 파일 단위로 끊어서 작업하도록 하여 해결했다.

개발 이후 뒤를 돌아봤다

개발 전 세웠던 목표는 일부 달성했다.

  • git의 기능 중 커밋 / 리셋 / 체크아웃 / 브랜치 기능은 정상 사용이 가능했다.
  • 병합의 경우, 데이터 형태 변환의 실패로 git 알고리즘을 통해 수행하지는 못하지만 어쨌든 병합 기능은 동작하므로 달성했다고 치려고 한다.

꼭 있었으면 했던 기능은 아니지만, 언급한 언로드의 한계로 달성하지 못한 것은 아래와 같다.

  • 플레이어 데이터의 버전관리

이건 잘했다!

  • 문서화를 굉장히 열심히 했다. 위에도 적은 이 문서를 봐도, 이렇게 열심히 문서화한 적이 없었던 것 같다.
  • 꾸준히 작업해서 무언가의 결과물을 만들어냈다. 항상 중간에 잊혀지던 다른 프로젝트들과는 달랐다. 굉장해!

이건 좀 아쉬웠다.

  • 결과물을 피드백해줄 사람이 없었다. 좁은 인간관계가 너무 서럽다...
  • 조금 지나치게 빡세게 개발했다. 여유있게 천천히 했어도 좋았을 것 같다.

이런 걸 알게됐다!

  • git에 대해 조금 더 잘 알게되었다. 원래 커밋/푸시밖에 할 줄 몰랐는데, 브랜치/체크아웃의 개념과 병합이 무엇인지, 병합 충돌이 왜 발생하고 발생한 충돌을 해결하는 방식에 대해 알게됐다.
  • 작업시간이 긴 로직을 관리하다보니 kotlin 의 Coroutine을 사용하게 되었는데, 덕분에 아주 조금이지만 suspend 함수 및 Coroutine에 대해 알게되었다.
  • NBT의 파싱을 통해 byte 단위로 파일을 읽고 쓰는 로직의 작성에 조금 익숙해졌다.

진짜 후기

역시 만족스럽다는 말밖에 생각이 안난다.
명확한 결과물이 나왔고, 문서화도 꼼꼼하게 진행해서 기록도 남겼으며 문외한에 가까웠던 git 에 대해서도 조금 더 알게되었다.
알게된 것들도 위에 적은 것 말고도 마주친 벽들로 인해 더 많은 것들이 있으며, 무엇보다 거의 처음으로 혼자 진행한 프로젝트에 대해 명확한 끝을 내릴 수 있었던 프로젝트다.

앞으로 진행하는 프로젝트도 이정도만 되어주면 정말 좋을 것 같다는 생각을 했다.

키위새의 아무말 저장소
  • 개발
  • 마인크래프트
  • 생명과학II
  • 아무말
Blog Logo