无标题 - nói dối e blog

无标题

Quản lý bộ nhớ và đọc trước đa luồng của tài nguyên

Quản lý bộ nhớ và đọc trước đa luồng của tài nguyên

Quản lý bộ nhớ và tải trước đa luồng trong tài nguyên game mạng

Trong phát triển client game mạng, quản lý tài nguyên là một phần cực kỳ quan trọng, nơi thể hiện rõ chất lượng của engine game. Tôi đã dành nhiều thời gian nghiên cứu và thử nghiệm trong lĩnh vực này, và gần đây đã đăng hai bài viết liên quan trên blog cá nhân: Quản lý tài nguyên dựa trên thu gom rác và Tải tài nguyên động.

Trong quá trình tái cấu trúc engine gần đây, tôi lại phải suy nghĩ kỹ về thiết kế mô-đun này và nảy sinh một số ý tưởng mới (dù không hoàn toàn mới mẻ). Trong cả ngày hôm nay, tôi đã dành một nửa thời gian để suy nghĩ về thiết kế giao diện, liên tục chỉnh sửa tệp tiêu đề. Tôi quyết định ghi lại toàn bộ suy nghĩ để làm rõ quá trình tư duy của mình.

Với sự phát triển vượt bậc của công nghệ, game mạng PC hiện đại đã thay đổi hoàn toàn. Để đáp ứng nhu cầu trải nghiệm thị giác của người chơi và nhờ vào sự nâng cấp hiệu năng phần cứng, các tài nguyên dữ liệu client game giờ đây đã được tính bằng gigabyte. Trước khi hệ điều hành 64-bit phổ biến, không gian địa chỉ người dùng 2GB khả dụng trên Windows bắt đầu trở nên hẹp hòi. Điều này có nghĩa là dù bạn có cố gắng thế nào, phương pháp tải tài nguyên một lần mà không giải phóng (phương pháp “ngây thơ” hiệu quả trước đây) cũng không còn phù hợp. Các kỹ sư thiết kế engine buộc phải tìm cách xóa bớt dữ liệu khỏi bộ nhớ khi không cần thiết và tải lại khi cần thiết. Việc quản lý bộ nhớ hiệu quả đã trở thành mối quan tâm xuyên suốt trong sự nghiệp lập trình của tôi, và hiện trạng này đang buộc ngày càng nhiều người cùng nghiên cứu giải pháp.

Với sự phổ biến của công nghệ 3D, tài nguyên game 3D còn đối mặt với một vấn đề mới: tham chiếu chéo giữa các tài nguyên. Ví dụ: một kết cấu có thể được tham chiếu bởi nhiều mô hình khác nhau, nhưng chúng ta chỉ nên giữ một bản duy nhất trong bộ nhớ. Mối quan hệ này có thể rất phức tạp, đặc biệt trong việc xây dựng cảnh vật. Khi cần triển khai cảnh vật liền mạch quy mô lớn, mối liên kết trực tiếp hoặc gián tiếp giữa các đối tượng ở các khu vực bản đồ sẽ ngày càng nhiều. Cây bên cạnh nhà, nhà bên cạnh cây, nhân vật di chuyển trong cảnh vật liên tục kích hoạt yêu cầu tải tài nguyên mới và giải phóng tài nguyên cũ.

Ban đầu tôi muốn áp dụng kỹ thuật thu gom rác (GC) để đơn giản hóa vấn đề, nhưng hiện tại suy nghĩ đã thay đổi. Bây giờ tôi muốn giữ lại toàn bộ “tiêu đề” (metadata) của tài nguyên trong bộ nhớ, và chỉ dỡ bỏ phần dữ liệu không cần thiết khi không sử dụng. Trong giai đoạn phát triển game, các mối quan hệ tham chiếu giữa tài nguyên sẽ được xây dựng sẵn, nhờ đó toàn bộ tiêu đề tài nguyên sẽ được nạp dần vào bộ nhớ, hình thành mạng lưới quan hệ giữa tất cả tài nguyên. Khi quản lý các dữ liệu này, chúng ta không cần đếm tham chiếu hay chuỗi GC. Với kiến trúc hệ thống hiện tại, lượng bộ nhớ tiêu thụ hoàn toàn kiểm soát được.

Trong quá trình vận hành phần mềm, sẽ có rất nhiều yêu cầu cấp phát bộ nhớ không bao giờ được giải phóng (trừ khi tiến trình kết thúc). Chúng ta có thể thiết kế một bộ cấp phát bộ nhớ đặc biệt cho nhiệm vụ này. Thuật toán thực hiện cực kỳ đơn giản: tăng dần con trỏ heap và yêu cầu hệ thống cấp thêm trang nhớ khi không đủ, hoặc dừng lại khi đủ.

Bây giờ hãy xem phương án tải tài nguyên động:

Rõ ràng, tải tài nguyên đa luồng là ưu tiên hàng đầu, tuy nhiên chúng ta cũng không loại trừ phương án đơn luồng. Ngoài ra, một số tài nguyên có thể được tính toán động hoặc không tải từ ổ cứng. Do đó, hệ thống cần hỗ trợ nhiều bộ tải khác nhau. Các bộ tải này cần được trừu tượng hóa để thuận tiện cho việc phát triển mở rộng engine sau này. Khung quản lý tài nguyên không cần quan tâm đến chi tiết xử lý của bộ tải, nghĩa là không chứa mã liên quan đến đa luồng, nhưng phải cung cấp giao diện thuận tiện cho tải bất đồng bộ.

Người dùng engine càng biết ít chi tiết kỹ thuật càng tốt. Họ chỉ cần thông báo với engine rằng cần tải tài nguyên gì, thường thông qua chuỗi định vị hoặc thông tin liên kết. Đôi khi cũng cần các yêu cầu bổ sung, ví dụ thông báo với module quản lý tài nguyên khi cần thu hồi bộ nhớ khi hệ thống khan hiếm tài nguyên.

Đối với các nhà phát triển bộ tải, chúng ta cần tiết lộ nhiều thông tin nội bộ hơn, nhưng không phải toàn bộ. Bộ tải không cần hiểu cấu trúc dữ liệu bộ nhớ của manager, thậm chí không cần biết cấu trúc nút dữ liệu tài nguyên. Nó chỉ cần cung cấp hàm callback để tải dữ liệu. Tuy nhiên, vì cần hỗ trợ tải bất đồng bộ, thiết kế giao diện callback cần truyền một thứ tương tự máy trạng thái để có thể tải dữ liệu theo từng bước.

Với những yêu cầu này, chúng ta có thể bắt tay vào thiết kế.

Tôi muốn giả định rằng dữ liệu tài nguyên được đặt trong một “cơ sở dữ liệu” trên ổ cứng, thực tế có thể dùng hệ thống tệp cục bộ mô phỏng, hoặc đóng gói thành tệp dữ liệu khi phát hành. Tôi có thể dùng chuỗi phân đoạn để định vị tài nguyên cụ thể trong cơ sở dữ liệu; nhưng không phải tài nguyên nào cũng cần tên chuỗi, các tài nguyên ẩn danh chỉ cần ID số duy nhất tồn tại trong cơ sở dữ liệu, và ID này có thể xuất hiện trong thông tin liên kết của tài nguyên khác. Ví dụ, hầu hết file kết cấu không cần tên rõ ràng, chúng chỉ được mô hình tham chiếu, không do engine tải trực tiếp.

Việc tải tài nguyên thường chia thành hai giai đoạn: tải tiêu đề và tải toàn bộ dữ liệu. Tiêu đề rất nhỏ, có thể lưu trữ vĩnh viễn trong RAM. Qua tiêu đề có thể biết kích thước dữ liệu, thông tin này hỗ trợ ra quyết định cho manager. Ngoài ra, việc tải tài nguyên ẩn danh còn có giai đoạn bổ sung: ánh x

0%