Skip to content
isdnetworks
Go back

화면을 오갈수록 죽었다

게임을 오래 켜 두면 느려지다가 죽는다. 몇 분 켜 두는 것만으로는 안 죽고, 화면을 여러 번 오갈 때 그렇다.

Table of contents

Open Table of contents

화면을 오갈수록 죽는다

먼저 언제 죽는지부터 갈랐다. 켜자마자 죽지는 않는다. 가만히 두면 몇십 분을 켜 놔도 안 죽는다. 화면을 왔다 갔다 하면 죽는다. 몇 번 만에 죽는지 세어 보니 스무 번쯤에서 눈에 띄게 느려지고 서른 번쯤에서 죽었다. 횟수가 대략 일정하다는 건 시간이 아니라 전환 자체에 딸린 것이 쌓인다는 뜻으로 보였다.

adb shell dumpsys meminfo 를 반복해 찍으면서 다시 해 봤다. 화면을 전환할 때마다 늘어나고 원래 화면으로 돌아와도 줄지 않는다. 늘기만 하고 안 줄어드니 어딘가에서 만든 것이 안 지워지고 있다.

느는 자리가 Dalvik 힙이 아니라 네이티브 쪽이었다. 텍스처는 NDK 와 OpenGL 이 들고 있어서 Java 쪽 도구로는 안 보인다.

코드를 봤다. 화면을 만들 때 적을 새로 만들어 자식으로 붙인다. 그런데 화면을 나갈 때 지우는 코드가 없다.

Enemy* e = new Enemy();
this->addChild(e);

쌓는 화면과 버리는 화면이 있었다

지우는 코드가 없는 게 곧 잘못은 아니었다. Cocos2d-x 의 CCObject 는 참조 계수 방식이라 addChild 로 붙이면 계수가 올라간다. 부모가 정리될 때 자식도 같이 정리된다. 그러면 부모가 정리되지 않고 있다는 뜻이 된다.

화면을 바꾸는 코드를 봤다.

CCDirector::sharedDirector()->pushScene(newScene);

CCDirector.h 를 열어 보니 화면을 바꾸는 방법이 셋이었다. pushScene 은 지금 화면을 쌓아 둔 채로 새 화면을 올린다. replaceScene 은 지금 화면을 버리고 바꾼다. popScene 은 쌓아 둔 것을 꺼낸다. 예제를 보고 따라 쓴 것이라 차이를 모르고 골랐다. 오갈 때마다 CCScene 이 그대로 쌓이고 있었다.

전부 replaceScene으로 바꾸면 될 것 같았지만 그러면 안 되는 자리가 있었다. 일시정지 화면은 게임으로 돌아와야 한다. 돌아올 화면이면 쌓아 두는 것이 맞고, 대신 popScene으로 꺼내야 한다. 쌓기와 꺼내기가 짝이고 짝이 안 맞으면 쌓인다. 화면마다 돌아올 것인지를 기준으로 나눠서, 메뉴에서 게임으로 갈 때와 게임에서 결과로 갈 때는 버리고, 게임에서 일시정지로 갈 때만 쌓게 고쳤다.

직접 만든 것은 직접 지운다

고치고 나서 다시 재 보니 많이 줄었는데 여전히 조금씩 늘고 있었다. 코드를 더 보니 자식으로 붙이지 않고 만들기만 한 것이 있었다.

CCTexture2D* tex = new CCTexture2D();

new 로 만든 CCObject 는 참조 계수가 1 로 시작한다. 부모가 정리돼도 안 지워진다. 만든 쪽이 release() 를 불러 줘야 한다. 아니면 autorelease() 를 붙여 다음 프레임에 풀리게 한다.

두 경우를 놓고 보니 선이 하나 나온다. 자식으로 붙인 것은 부모가 정리하고 직접 만든 것은 직접 정리한다. 누가 소유하느냐가 정하는 것이다.

다만 코드를 봐서 어느 쪽인지 바로 안 보이는 게 문제였다. 만드는 줄과 붙이는 줄이 멀리 떨어져 있으면 붙였는지 안 붙였는지 확인하러 위아래를 오가야 한다. 그래서 만들면 바로 다음 줄에서 붙이는 것을 습관으로 두기로 했다. 떨어져 있으면 중간에 다른 경로로 빠져나갈 때 안 붙은 채로 남을 수도 있다.

이미지 캐시 비우는 시점

CCTextureCachedumpCachedTextureInfo() 를 부르니 무엇이 얼마나 잡고 있는지 로그로 나왔다. 텍스처가 차지하는 몫이 컸다. CCTexture2D 를 직접 만드는 대신 addImage 로 부르면 캐시가 한 번 읽은 것을 재사용한다.

다만 캐시도 안 비우면 쌓인다. 안 쓰는 텍스처까지 계속 들고 있게 된다. removeUnusedTextures() 는 참조 계수가 1 인 것만 지운다. 어디서 부를지가 판단이었다. 자주 비우면 메모리는 적게 쓰는데 다시 읽느라 느려지고 안 비우면 빠른 대신 쌓인다.

화면이 크게 바뀔 때만 비우기로 했다. 메뉴에서 게임으로 들어갈 때처럼 쓰는 이미지가 통째로 달라지는 지점이다. 같은 계열 화면끼리 오갈 때는 어차피 다시 쓸 것이라 그냥 뒀다.

정리


Share this post on:

Previous Post
값을 하나 늘렸더니 다른 데서 안 잡혔다
Next Post
점수가 음수가 됐다