Hệ Thống Tập Tin Ảo Của Động Cơ Game - nói dối e blog

Hệ Thống Tập Tin Ảo Của Động Cơ Game

Động cơ game hiện tại mà chúng tôi đang sử dụng đã bắt đầu được phát triển từ năm 2018. Ngay từ đầu, nó được định hướng trở thành một công cụ game chủ yếu dành cho nền tảng di động, đặt tính tiện lợi cho lập trình di động làm ưu tiên hàng đầu. Vì rõ ràng chúng ta không thể trực tiếp viết code hay tạo tài nguyên trên thiết bị di động, môi trường phát triển và môi trường chạy game luôn được tách biệt. Ngay từ giai đoạn đầu, nhóm chúng tôi đã thiết kế một hệ thống tập tin ảo (Virtual File System - VFS) có khả năng ánh xạ hệ thống tập tin từ máy phát triển sang thiết bị di động qua mạng, đồng thời tích hợp chức năng quản lý phiên bản. Nhờ đó, trong quá trình phát triển, hầu hết các chỉnh sửa trên máy phát triển đều có thể phản ánh ngay lập tức lên thiết bị di động.

Phần lớn động cơ game của chúng tôi được viết bằng ngôn ngữ Lua, điều này đồng nghĩa với việc hệ thống tập tin không chỉ chứa tài nguyên của game mà còn bao gồm luôn cả code. Đáng chú ý, ngay cả phần cài đặt của chính hệ thống VFS cũng nằm trong đó. Câu chuyện này phức tạp hơn so với dự tính ban đầu, nhóm chúng tôi đã phải liên tục tinh chỉnh hệ thống này qua nhiều năm trời, đến tận gần đây mới tạm ổn định. Chẳng hạn, phần tự khởi động (bootstrapping) - từng được coi là khâu khó nhất - đã được loại bỏ vào năm ngoái để giảm độ phức tạp của hệ thống.

Giải thích ngắn gọn về vấn đề tự khởi động:
Thông thường, người ta thiết kế cơ chế tự cập nhật, cho phép cập nhật phiên bản mới và lưu trữ vĩnh viễn trên bộ nhớ ngoài khi phát sinh nhu cầu nâng cấp hoặc sửa lỗi, mà không cần đóng gói lại ứng dụng. Hệ thống phải hoàn thành cập nhật trong quá trình khởi động, khởi tạo thành công phiên bản mới và khởi động động cơ một cách liền mạch (hệ thống VFS phải được khởi tạo trước toàn bộ động cơ). Tuy nhiên, trong môi trường đặc biệt như iPhone, việc sử dụng nhiều tiến trình hoặc khởi động lại tiến trình gặp nhiều trở ngại. Chưa kể, nếu cập nhật thất bại, cần có cơ chế tự phục hồi để quay về phiên bản trước mà không cần cài đặt lại ứng dụng.

Phiên bản đầu tiên của chúng tôi cơ bản hoàn thành nhiệm vụ, nhưng việc đưa vào quá trình khởi động phức tạp luôn gây cảm giác bất an. Vì vậy, trong lần tái cấu trúc năm ngoái, nhóm quyết định loại bỏ toàn bộ phần này, thay thế bằng phiên bản đơn giản hơn nhưng không hỗ trợ tự cập nhật. Tuy nhiên, bài toán cập nhật vẫn chưa được giải quyết trọn vẹn. Trong đa số trường hợp, chúng tôi chọn cách đóng gói lại ứng dụng và phát hành phiên bản mới. Ngoài ra, tùy trường hợp cụ thể, chúng tôi cũng có thể cập nhật hệ thống VFS ngay trong bộ nhớ (thay vì cập nhật vĩnh viễn trên đĩa). Nhờ tính năng động của Lua, ngay cả sau khi khởi động, chúng tôi vẫn có thể sửa đổi các hàm API đã tồn tại trong bộ nhớ thông qua code nghiệp vụ. Thêm nữa, hệ thống VFS hoạt động trên framework đa nhiệm ltask, trong đó thành phần cốt lõi là một luồng IO độc lập chạy trên máy ảo Lua riêng biệt, điều này tạo điều kiện thuận lợi để cập nhật riêng lẻ hệ thống VFS trong bộ nhớ. Khi có sự cố xảy ra, vì không ảnh hưởng đến bộ nhớ ngoài nên việc khôi phục hệ thống cũng đơn giản hơn nhiều.

Nhân tiện, việc sử dụng máy ảo độc lập trong ltask确实 giúp tăng độ ổn định cho hệ thống VFS, nhưng cũng gây ra một số rắc rối mới: Phần lớn code của ltask cũng được viết bằng Lua, và chính các đoạn code này lại nằm trong chính hệ thống VFS. Đây chính là yếu tố thúc đẩy nhóm đơn giản hóa phần tự khởi động của VFS.

Bài viết hôm nay muốn đào sâu vào một vấn đề khác: Ban đầu, VFS được thiết kế phục vụ cho động cơ game, và động cơ giả định rằng hệ thống tập tin này là chỉ đọc. Các thao tác ghi như lưu dữ liệu người chơi sẽ được xử lý bên ngoài VFS. Nói cách khác, cấu trúc tập tin trong kho tài nguyên VFS được xác định cố định ngay lúc game khởi động. Nhờ giả định này, nhóm mới có thể xây dựng hệ thống đơn giản và tích hợp cơ chế quản lý phiên bản kiểu Git. Trong VFS, cấu trúc thư mục hoạt động như một cây Merkle, bất kỳ phiên bản tập tin hay thư mục nào cũng có thể truy xuất thông qua giá trị hash. Tuy nhiên, thiết kế này hoàn toàn dựa trên nguyên tắc kho tài nguyên không thay đổi trong quá trình chạy chương trình.

Trong quá trình phát triển, nhóm phát hiện ra nhiều ứng dụng xây dựng trên động cơ này vi phạm nguyên tắc trên. Ví dụ điển hình là trình biên tập (editor) - một sản phẩm được xây dựng trên nền động cơ. Trình biên tập tất yếu phải thay đổi các tập tin cục bộ, mà những tập tin này sau này lại trở thành tài nguyên mà động cơ cần đọc. Một ví dụ khác là chính bản thân VFS được cấu trúc theo mô hình C/S. Phần lõi C của động cơ sẽ cache các tập tin VFS lên hệ thống tập tin vật lý cục bộ, nhưng đây chỉ là bản cache tạm thời. Nguồn gốc dữ liệu đến từ phía S thông qua đồng bộ mạng. Chúng tôi đã phát triển một công cụ tên là fileserver để hỗ trợ phía S. Rõ ràng, chính fileserver cũng phá vỡ quy tắc “kho tài nguyên VFS không thay đổi trong runtime” mà chúng tôi từng tuân thủ nghiêm ngặt.

Trước thực trạng này, nhóm buộc phải phát triển hai phiên bản VFS:

  1. Phiên bản C trong mô hình C/S,
  2. Phiên bản độc lập, có thể truy cập trực tiếp hệ thống tập tin cục bộ (có thêm các hàm ghi dữ liệu).

Hai phiên bản này dù tồn tại song song nhiều năm với giao diện API gần như tương đồng, nhưng đôi khi lại phản ứng khác nhau trong các tình huống nhất định. Trong khi trên PC, chúng tôi vì tiện lợi thường dùng phiên bản 2 (dễ debug hơn), thì trên thiết bị

0%