Tái Cấu Trúc
Với việc phát triển engine đang đi vào giai đoạn cuối, vài tháng gần đây tôi đã dành thời gian hoàn thiện những phần nhỏ nhặt còn lại, đồng thời hỗ trợ đào tạo đồng nghiệp ở các nhóm khác. Vì làm việc từ xa nên việc cấu hình VPN và tường lửa đã khiến tôi đau đầu suốt vài ngày trời.
Trước kỳ nghỉ Trung Thu, tôi quyết định dành thời gian suy nghĩ lại những ý tưởng đã thiết kế từ năm ngoái nhưng chưa có dịp thực hiện - cơ chế đọc trước đa luồng tài nguyên. Ban đầu khi thiết kế module quản lý tài nguyên, do gấp gáp nên một số yêu cầu phát sinh về sau khiến chúng tôi phải điều chỉnh. Chẳng hạn, chúng tôi cần sử dụng đồng thời nhiều module tải tài nguyên khác nhau từ hệ thống tệp tin cục bộ và các tập tin đóng gói. Ban đầu tôi cho rằng trong giai đoạn phát triển không cần đóng gói tài nguyên, nhưng khi số lượng dữ liệu tăng lên đáng kể, việc đóng gói các tập tin ít thay đổi trở nên cần thiết hơn bao giờ hết. Điều này đòi hỏi phải xây dựng cơ chế tải hỗn hợp.
Điểm khó khăn đầu tiên nằm ở việc chúng tôi đã tích hợp quan hệ phụ thuộc giữa các tập tin vào hệ thống tệp tin từ giai đoạn đầu. Sự khác biệt lớn giữa hệ thống tệp tin tự thiết kế và hệ thống tệp tin cục bộ khiến việc triển khai tải hỗn hợp trở nên phức tạp. Cụ thể, hệ thống tệp tin của chúng tôi hoạt động theo nguyên tắc tương tự ext2 trên Linux, mỗi tập tin có inode riêng mô tả quan hệ với các tập tin khác nhưng lại không nhất thiết phải có tên. Điều này mâu thuẫn với hệ thống tệp tin truyền thống, gây khó khăn trong việc biểu diễn quan hệ phụ thuộc.
Thứ hai, dù đã tính đến khả năng tải đa luồng từ đầu, nhưng do nhiều thay đổi phát sinh như yêu cầu upload dữ liệu lên GPU trong quá trình tải, việc đảm bảo an toàn đa luồng trở nên cực kỳ phức tạp. Mỗi loại tập tin đều có mã xử lý riêng, trong khi phần lớn engine lại được thiết kế theo mô hình đơn luồng. Việc áp dụng đa luồng sẽ tạo ra quá nhiều hạn chế cho các lập trình viên sau này khi mở rộng loại tập tin hỗ trợ.
Xuất phát từ hai vấn đề trên, tôi quyết định tiến hành đại tu toàn bộ module quản lý tài nguyên. Để không ảnh hưởng đến tiến độ nhóm, tôi ước tính thời gian hoàn thành và chọn kỳ nghỉ Trung Thu để thực hiện. Tuy nhiên mọi việc không diễn ra suôn sẻ như dự kiến.
Ban đầu tôi dự tính 3000 dòng code sẽ hoàn thành dễ dàng trong 3 ngày nghỉ. Nhưng do lịch trình cá nhân, tôi chỉ bắt tay vào làm vào chiều ngày thứ ba. Phiên bản hiện tại có một số thiết kế hơi “quá tay” khi dành nhiều độ linh hoạt cho cơ chế đa luồng chưa triển khai. Điều này khiến việc đảm bảo an toàn đa luồng đòi hỏi phải áp đặt nhiều hạn chế hơn tôi mong muốn lên lập trình viên.
Để tách quá trình phân tích tài nguyên khỏi luồng chính, tôi thiết kế một module CRT độc lập. Tuy nhiên, càng làm tôi càng nhận ra rằng việc đảm bảo tuyệt đối an toàn đa luồng sẽ khiến việc mở rộng trở nên gò bó. Hoặc là chấp nhận đánh đổi hiệu suất bằng cách dùng khóa chặn, hoặc phải giữ lại một số phần không an toàn trong luồng chính - cả hai đều đi ngược lại mục tiêu ban đầu.
Vấn đề thứ hai liên quan đến việc xung đột inode giữa các module tải khác nhau. Trong định dạng gói tự thiết kế, mỗi tập tin có id riêng, còn hệ thống tệp tin cục bộ lại tự động sinh id duy nhất. Tôi quyết định xử lý theo hai giai đoạn: trước tiên giải quyết vấn đề tải hỗn hợp, sau đó loại bỏ toàn bộ code cũ và viết lại từ đầu.
Giai đoạn một diễn ra thuận lợi với vài trăm dòng code sửa đổi. Trong khi chuẩn bị cho giai đoạn hai, tôi dành nhiều ngày trước kỳ nghỉ để phác thảo kiến trúc mới trên giấy. Lần này tôi chia module quản lý tài nguyên thành ba tầng đơn giản hơn:
- Tầng giao diện người dùng quản lý nhiều module tải khác nhau
- Tầng cache tài nguyên với hai cơ chế: cache theo cấu trúc cây thư mục và cache ánh xạ inode
- Tầng module tải thực sự đảm nhiệm việc ánh xạ tên tập tin sang id, xác định phụ thuộc, đọc header và phân tích dữ liệu
Về vấn đề đọc trước đa luồng, tôi quyết định tận dụng cơ chế của hệ điều hành bằng cách mở riêng một luồng “chạm nhẹ” vào các tập tin tiềm năng. Đây là giải pháp đơn giản nhưng hiệu quả, tránh được các rắc rối về đồng bộ hóa.
Ngày đầu tiên tôi viết mới 1000 dòng code, thay thế 2000 dòng code cũ và sửa đổi hơn chục file liên quan. Tuy nhiên tiến độ chậm hơn dự kiến nên tôi đành tạm nghỉ để tiếp tục vào hôm sau. Trong vài ngày tiếp theo, tôi cùng hai đồng nghiệp chính tiến hành viết lại các module phân tích tài nguyên theo chuẩn mới. May mắn là việc chia module rõ ràng giúp chúng tôi kiểm thử độc lập và tích hợp dần từng phần.
Đến chiều thứ năm, sau khi hoàn thành phần lớn công việc, tôi mở một chai nước ngọt để ăn mừng. Lulu đùa rằng với quy mô đại tu như vậy, chắc chắn sẽ có khoảng 5 lỗi nghiêm trọng. Và đúng như dự đoán, lần chạy demo đầu tiên đã gây ra lỗi core dump. Sau khi sửa 3 lỗi đầu tiên, chương trình chạy ổn định, nhưng đến tối lại phát hiện thêm 2 lỗi nữa - tổng cộng đúng 5 lỗi như lời tiên đoán!
Ngày hôm sau, khi kiểm tra chức năng unload và reload toàn bộ tài nguyên, chương trình lại gặp sự cố. Sau hơn 4 giờ vật lộn, chúng tôi phát hiện ra lỗi ngu ngốc ở bộ cấp phát