Game Studio
Liên kế mạng xã hội

Game Studio


Quản lý bộ nhớ trong Cocos2d-x

Giới thiệu

Nếu bạn là người mới bắt đầu làm quen với Cocos2d-x, chắc hẳn đa số các bạn đều cảm thấy bỡ ngỡ trước cách quản lý bộ nhớ của Cocos2d-x. Bạn sẽ không thấy bất kỳ hàm new hay delete nào trong một game “thuần” Cocos2d-x. Vậy làm thế nào Cocos2d-x có thể cấp phát/thu hồi bộ nhớ của các đối tượng được? Bài viết này sẽ giải đáp thắc mắc của bạn.

Tiền đề bài viết

Nhiều học trò của tôi, mới làm quen hoặc đã có kinh nghiệm làm việc với Cocos2d-x, vẫn chưa thực sự hiểu bản chất về cách quản lý bộ nhớ của engine làm game mạnh mẽ này. Tôi viết bài này để bổ sung kiến thức nền tảng giúp cho các em làm việc tốt hơn sau khi ra trường.

Đối tượng hướng đến

Bài viết dành cho các lập trình viên mới làm quen với Cocos2d-x chưa hiểu cách quản lý bộ nhớ của Cocos2d-x hoặc các lập trình viên muốn tìm hiểu thêm về cách quản lý bộ nhớ mới.

Reference Counting

Reference counting là một kỹ thuật để lưu trữ số lượng tham khảo, con trỏ đến một tài nguyên như các đối tượng, vùng nhớ hoặc các tài nguyên khác. Tôi sẽ lấy ví dụ để các bạn hiểu hơn về Reference counting.

  1. int main()
  2. {
  3. int *pi = new int;
  4. *pi = 10;
  5. int *pi_1 = pi;
  6. int *pi_2 = pi;
  7.  
  8. return 0;
  9. }

Trong ví dụ trên, tài nguyên mà tôi đề cập ở trên là một vùng nhớ dùng để lưu trữ số nguyên kiểu int. Sau khi chương trình chạy dòng thứ 3, có 1 con trỏ “nắm giữ” tài nguyên. Sau khi chương trình chạy dòng thứ 5, 6 sẽ có lần lượt 2, 3 con trỏ “nắm giữ” tài nguyên. Reference counting là một kỹ thuật để lưu trữ những con số này.

Nhược điểm của cách quản lý bộ nhớ thông thường

Với cách quản lý bộ nhớ chỉ sử dụng 2 toán tử new và delete, rất khó để có thể biết được một tài nguyên còn được sử dụng hay không. Điều này sẽ gây khó khăn cho lập trình viên khi phải quyết định nên giải phóng hay giữ lại tài nguyên đã cấp phát. Ví dụ:

  1. int main()
  2. {
  3. int *pi = new int;
  4. *pi = 10;
  5. int *pi_1 = pi;
  6. int *pi_2 = pi;
  7.  
  8. delete pi_1;
  9.  
  10. *pi = 11;
  11. *pi_2 = 12;
  12.  
  13. return 0;
  14. }

Tại dòng 8, lập trình viên của chúng ta không muốn sử dụng pi_1 nữa nên họ đã giải phóng tài nguyên được quản lý bởi pi_1. Điều đó cực kỳ nguyên hiểm vì ngoài pi_1 ra vẫn còn pi và pi_2 “nắm giữ” và có nhu cầu sử dụng tài nguyên chung này! Việc sử dụng tài nguyên dùng chung không hợp lý có thể làm cho chương trình của chúng ta phải “dừng lại khi chưa được phép”.

CCAutoreleasePool

CCAutoreleasePool là một phương pháp để quản lý bộ nhớ trong Cocos2d-x dựa trên kỹ thuật Reference counting. Các tài nguyên trong pool có reference counting bằng 0 sẽ tự động được giải phóng lúc kết thúc mỗi lần lặp (message loop).

Retain

Khi bạn gọi ptr->retain(), bạn đã báo cho chương trình biết con trỏ ptr nắm giữ tài nguyên nó đang trỏ đến. Reference counting sẽ tăng lên 1 đơn vị.

Release

Khi bạn gọi ptr->release(), bạn đã báo cho chương trình biết con trỏ ptr không còn nắm giữ tài nguyên nó đang trỏ đến nữa. Reference counting sẽ giảm xuống 1 đơn vị.

Autorelease

Khi bạn gọi object->autorelease(), điều đó đồng nghĩa với việc bạn đưa đối tượng object vào CCAutoreleasePool. Tài nguyên được tham khảo bởi object sẽ được CCAutoreleasePool quản lý.

Ví dụ minh hoạ

  1. bool StdioMemManLayer::init()
  2. {
  3. _pSprite1 = CCSprite::create("texture_1.png");
  4. this->addChild(_pSprite1);
  5.  
  6. _pSprite2 = CCSprite::create("texture_2.png");
  7. }
  8.  
  9. void StdioMemManLayer::logRotation()
  10. {
  11. CCLOG("_pSprite1 rotation: %.2f", _pSprite1->getRotation());
  12. CCLOG("_pSprite2 rotation: %.2f", _pSprite2->getRotation());
  13. }

Các đối tượng CCSprite trong Cocos2d-x sẽ tự động gọi phương thức autorelease khi được tạo bởi phương thức create. Điều đó có nghĩa là đối tượng này được quản lý bởi CCAutoreleasePool.

_pSprite1 sau khi khởi tạo được thêm vào layer StdioMemManLayer sẽ được layer này nắm giữ tài nguyên, vì vậy lệnh gọi hàm tại dòng 11 sẽ không xảy ra sự cố gì cả. Khác với _pSprite1, _pSprite2 sau khi khởi tạo đã không có “con trỏ” nào nắm giữ tài nguyên được trỏ tới bởi _pSprite2 nên lệnh gọi hàm tại dòng 12 sẽ gây crash chương trình vì CCAutoreleasePool đã giải phóng vùng nhớ _pSprite2 trỏ đến. Để đảm bảo câu lệnh này chạy đúng, lập trình viên phải gọi lệnh sau:

  1. _pSprite2->retain();

Tổng kết

Phương pháp quản lý bộ nhớ trong Cocos2d-x là một kiến thức thú vị. Nó không chỉ được áp dụng trong Cocos2d-x mà còn được ứng dụng trong rất nhiều hệ thống dọn rác (Garbage Collector) hiện nay. Vì vậy mặc dù bạn không phải là lập trình viên sử dụng Cocos2d-x, việc tìm hiểu những kiến thức này vẫn rất cần thiết cho công việc của bạn.

Theo: Stdio