Ghi Chú Phát Triển (5): Dịch Vụ Cảnh Quan Và Tránh Khóa Đọc Ghi - nói dối e blog

Ghi Chú Phát Triển (5): Dịch Vụ Cảnh Quan Và Tránh Khóa Đọc Ghi

Tuần này tôi bắt đầu triển khai mô-đun cảnh quan. Vì tất cả các thực thể người chơi (PC) ở phía máy chủ đều hoạt động độc lập dưới dạng các đại lý riêng biệt, việc tách biệt này đòi hỏi một mô-đun trung gian để kết nối chúng. Trong giai đoạn đầu, mục tiêu là xây dựng một dịch vụ cảnh quan đơn giản nhằm đồng bộ hóa trạng thái thế giới mà mỗi đại lý quan sát được.

Vấn đề này đã được tôi suy nghĩ kỹ trước đây. Các yêu cầu chính được tổng hợp như sau:

  1. Mỗi đại lý cần nắm bắt được các thay đổi đang diễn ra trong cảnh quan.
  2. Khi một đại lý gia nhập cảnh quan, nó phải nhận được toàn bộ trạng thái hiện tại của thế giới.
  3. Đại lý cần truy xuất được trạng thái của chính mình tại thời điểm rời khỏi cảnh quan. Thông tin vị trí của nhân vật trong cảnh quan do dịch vụ cảnh quan quản lý, chi tiết đã đề cập trong Ghi chú phát triển (1).

Việc đồng bộ hóa dữ liệu quy mô lớn đặt ra yêu cầu cao về hiệu năng. Trong một cảnh quan có N nhân vật, lượng dữ liệu đồng bộ cần xử lý là N². Dù ưu tiên ban đầu là thiết kế giao diện, tối ưu hóa sẽ thực hiện ở giai đoạn sau, nhưng thiết kế giao diện lại quyết định khả năng tối ưu hóa của phần triển khai. Do đó, cần thận trọng trong từng bước thiết kế.

Ví dụ, giao diện đồng bộ và bất đồng bộ có bản chất khác nhau.

  • Giao diện bất đồng bộ: Mô hình yêu cầu-phản hồi trực quan hơn, linh hoạt hơn khi tách biệt tiến trình, nhưng khó tối ưu hiệu năng và phức tạp trong quy trình sử dụng. Tuy nhiên, kỹ thuật coroutine có thể giải quyết phần nào khó khăn này. Ngay cả khi ép tất cả tiến trình (không nhất thiết là tiến trình hệ điều hành) chạy trên cùng một CPU để loại bỏ lớp giao tiếp mạng, việc sao chép dữ liệu vẫn là vấn đề lớn. Dù có các giải pháp zero-copy, việc vượt qua ranh giới ngôn ngữ lập trình vẫn rất khó khăn.
  • Giao diện đồng bộ: Dễ sử dụng hơn, nhưng nếu chưa tối ưu, hiệu năng có thể kém đến mức không thể sử dụng được. Nếu sau này cần thay đổi giao diện, hệ thống sẽ phải chịu ảnh hưởng nặng nề. Trong khi đó, giao diện bất đồng bộ vì đã tính đến độ trễ từ đầu nên dù chưa tối ưu, hệ thống vẫn vận hành ổn định.

Sau khi phân tích, tôi quyết định sử dụng giao diện đồng bộ vì hiệu năng trong trường hợp này là chấp nhận được. Người dùng có thể trực tiếp truy vấn trạng thái cảnh quan. Đồng nghiệp Tiểu Niễu đề xuất phương án đơn giản hóa bằng cách tích hợp tất cả đại lý và dịch vụ NPC tương lai vào cùng một mô-đun chia sẻ trạng thái cảnh quan. Nhờ khung coroutine, các tiến trình này không xung đột lẫn nhau.

Tôi không phản đối việc chạy toàn bộ cảnh quan trên một CPU về mặt vật lý, nhưng vẫn nhấn mạnh thiết kế độc lập. Dù tính đồng thời của đại lý không tồn tại trong triển khai thực tế, nó cần được tính đến trong thiết kế. Các đại lý hoàn toàn độc lập về mặt vật lý, nhưng tương tác qua các kỹ thuật tối ưu để đạt gần như chi phí bằng không.

Việc tách biệt vật lý mô-đun là điều kiện đủ để hệ thống có độ bền và linh hoạt cao, đồng thời giảm thiểu phụ thuộc vào chất lượng của từng mô-đun. Về chi phí hiệu năng, tôi cho rằng tích hợp mọi thứ vào một chỗ sẽ giảm chi phí giao tiếp (ví dụ: cùng chạy trong một trạng thái Lua, không có truyền dữ liệu qua ranh giới ngôn ngữ), nhưng đây thực chất là cách trì hoãn chi phí. Bỏ qua nguy cơ giảm độ bền, chi phí garbage collection (GC) trong ngôn ngữ động cũng khó dự đoán từ đầu.

Dù chọn phương án triển khai nào, thiết kế giao diện luôn quan trọng hơn cả. Kinh nghiệm cho thấy việc tách biệt rồi hợp nhất sau này khó hơn nhiều so với hợp nhất rồi tách biệt sau. Lịch sử đã chứng minh điều này qua hàng loạt hệ thống C++ với hàng chục nghìn dòng code, hàng chục lớp đan cài lộn xộn, thậm chí phải dùng “dây nối chéo” để giao tiếp giữa các lớp. Nếu ngôn ngữ tĩnh còn như vậy, ngôn ngữ động với tính linh hoạt cao càng dễ gặp vấn đề.

Tuần này, nhóm Monster Studio và Xiao V đang bận rộn với việc phối hợp mỹ thuật và phát triển plugin công cụ ở phía client, khiến việc tích hợp C/S liên tục bị hoãn. Sau khi viết khoảng 200-300 dòng mã C cho dịch vụ cảnh quan, tôi tạm gác lại để tiếp tục hoàn thiện giao diện. Một số ý tưởng tối ưu gần đây cũng được ghi chú lại, chưa cần triển khai ngay.

Về vấn đề đọc đồng thời trạng thái cảnh quan:
Dữ liệu cảnh quan không yêu cầu nhất quán tuyệt đối ở phía người đọc. Vì đây là dữ liệu tích lũy cục bộ và thay đổi chậm, người đọc không cần phiên bản mới nhất mà chỉ cần một phiên bản toàn vẹn. Ví dụ: đọc trạng thái cảnh quan từ 0.05 giây trước là hoàn toàn chấp nhận được. Với con người, 0.05 giây là khoảnh khắc cực ngắn, nhưng với máy tính, đây là khoảng thời gian đủ dài.

Độ trễ này vốn đã tồn tại trong giao tiếp mạng, nơi 50ms đã là tốc độ phản hồi tốt. Nếu đồng bộ đọc từ bộ nhớ cục bộ, bất kỳ thao tác đọc nào cần tính nguyên tử đều mất ít hơn 0.

0%