Nén, Đóng Gói Và Cập Nhật Bản Vá Tài Nguyên Trò Chơi
9 năm trước, tôi đã thiết kế định dạng dữ liệu cho gói tài nguyên và gói bản vá của trò chơi NetEase. Mục tiêu ban đầu là tạo ra một hệ thống dễ phân tích, định vị nhanh tệp tin trong gói, đồng thời tối ưu hóa việc cập nhật để tiết kiệm băng thông. Dù qua năm tháng, nhiều dự án đã cải tiến định dạng gói tài nguyên, nhưng về bản chất, cấu trúc cốt lõi vẫn giữ nguyên.
Ban đầu, chúng tôi đơn giản nối tiếp các tệp cần đóng gói, sau đó thêm một bảng chỉ mục ở cuối. Để định vị tệp nhanh chóng, tên tệp được băm thành giá trị hash, cho phép truy xuất trực tiếp. Tuy nhiên, tên tệp gốc không được lưu trong gói tài nguyên, mà được giữ riêng trong một tệp index ngoại tuyến. Tệp này không công khai, nên việc giải nén trực tiếp từ gói tài nguyên không thể khôi phục chính xác tên tệp.
Nhân tiện, định dạng MPQ của Blizzard cũng áp dụng nguyên tắc tương tự. Nếu không biết trước tên tệp, việc khôi phục tên tệp từ gói MPQ gần như bất khả thi. Nhiều công cụ giải nén MPQ trên mạng thường đi kèm danh sách tên tệp đặc thù cho từng trò chơi.
Khác với đa số định dạng gói tài nguyên khác (như MPQ của Blizzard), định dạng của chúng tôi cho phép “khoảng trống” giữa các tệp. Đây là giải pháp tối ưu cho các gói tài nguyên lớn. Nếu áp dụng cách truyền thống – xóa tệp khỏi gói thì phải đóng gói lại toàn bộ hoặc dời dữ liệu – sẽ gây ra lượng lớn thao tác I/O, làm chậm quá trình cập nhật. Ví dụ, khi cập nhật World of Warcraft hay StarCraft II, thời gian tải gói cập nhật thường ngắn hơn nhiều so với thời gian áp dụng bản vá lên gói tài nguyên hiện có.
Để tránh việc người dùng phải chờ đợi lâu do cập nhật thường xuyên, chúng tôi không dời dữ liệu khi xóa tệp. Thay vào đó, khoảng trống được giữ lại để tái sử dụng cho các tệp mới nhỏ hơn. Nếu không tận dụng được, khoảng trống đó sẽ bị lãng phí – tương tự như cơ chế quản lý bộ nhớ. Dần dần, gói tài nguyên sẽ xuất hiện nhiều “lỗ hổng”, nhưng điều này hoàn toàn chấp nhận được.
Một phương pháp cập nhật khác là đóng gói riêng các tệp cần sửa đổi, lưu chúng dưới dạng tệp mới (với hậu tố khác) trên ổ cứng người dùng. Khi đọc tài nguyên, engine trò chơi sẽ ưu tiên tìm kiếm trong gói cập nhật trước. Cách tiếp cận này cũng được áp dụng trong các trò chơi của Id Software như Quake/Doom.
Để tăng tốc độ cập nhật bản vá, chúng tôi không lưu trữ các tệp nhỏ trong gói vá. Thay vào đó, gói vá là một tệp diff nhị phân của toàn bộ gói tài nguyên, được tạo bằng phương pháp đóng gói tăng dần trên máy chủ. Nhờ cấu trúc đóng gói đặc biệt, tệp diff này có thể rất nhỏ, đặc biệt khi chỉ sửa đổi cục bộ một vài tệp.
Tuy nhiên, một số đồng nghiệp sau này đã đặt câu hỏi về hiệu quả của phương pháp này. Họ cho rằng việc tạo gói vá tăng dần tốn nhiều thời gian, trong khi việc lưu trữ các tệp nhỏ đã sửa đổi trực tiếp vào bản vá và để máy người dùng áp dụng vá theo từng tệp nhỏ có thể hiệu quả hơn.
Thực tế, hiệu quả của diff nhị phân có giới hạn, đặc biệt khi nhiều dự án chuyển sang định dạng dữ liệu văn bản – một thay đổi nhỏ cũng ảnh hưởng toàn bộ tệp. Tuy nhiên, thiết kế ban đầu có lý do lịch sử của nó: 10 năm trước, tốc độ I/O đĩa chậm, và trò chơi Đại Thoại Tây Du yêu cầu tải bản đồ lớn liên tục. Định dạng bản đồ được thiết kế đặc biệt để phù hợp với việc cập nhật cục bộ, đồng thời hiệu quả cho cả các tệp hình ảnh chưa nén.
Khi tổng kết thiết kế cũ, tôi nhận thấy cần cải tiến định dạng gói tài nguyên cho engine mới.
Cải tiến 1: Tích hợp Nén dữ liệu
Định dạng cũ chỉ tập trung vào đóng gói, việc nén do module cấp cao xử lý. Điều này liên quan đến yêu cầu truy cập ngẫu nhiên của tệp bản đồ Đại Thoại Tây Du và ảnh hưởng đến hiệu quả của diff nhị phân. Trong thiết kế mới, tôi đề xuất quản lý gói tài nguyên theo kiểu hệ thống tệp: chia tệp lớn thành các khối, quản lý bằng bảng FAT ảo. Mỗi khối có thể chọn nén hoặc không, đảm bảo khả năng nén dữ liệu đồng thời duy trì truy cập ngẫu nhiên (hỗ trợ lồng ghép các gói dữ liệu).
Cải tiến 2: Hỗ trợ Liên kết trong Gói tài nguyên
Trong giai đoạn phát triển, các tài nguyên có thể tồn tại độc lập mà không cần quan tâm đến mối quan hệ tham chiếu phức tạp. Ví dụ, tệp mô hình có thể chứa các texture đi kèm trong thư mục con. Khi đóng gói, hệ thống sẽ so sánh nội dung các tệp, loại bỏ các tệp trùng lặp và tạo liên kết thay thế. Engine sẽ tự động nhận diện mối quan hệ tham chiếu, đảm bảo mỗi tài nguyên chỉ được tải duy nhất một lần.
Cải tiến 3: Tối ưu Quy trình Tạo bản vá
Hiệu quả tạo bản vá trong phát triển cần được cân nhắc. Trong giai đoạn phát triển, một quy trình đơn giản và nhanh chóng giúp xây dựng bản dựng hàng ngày. Thay vì tính toán MD5 cho tất cả tệp trước để tăng tốc phân tích, tôi đề xuất tạo trực tiếp bản vá chứa thông tin diff của từng tệp nhỏ. Đồng thời, phát triển một công cụ patch thông minh:
- Trích xuất các tệp cần vá từ gói cũ.
- Kết hợp với thông tin diff từ bản vá để tạo gói cập nhật.
- Xóa các tệp cũ khỏi gói, nén các khoảng trống.
- Nối gói mới với phần còn lại của gói cũ.
Quá trình nén khoảng trống sẽ tiêu tốn một chút thời gian I/O trên máy người dùng, nhưng tốc độ vá vẫn chấp nhận được. Các tệp thường xuyên cập nhật có xu hướng tập trung ở cuối gói (do được