Ghi Chú Phát Triển (27): Máy Tính Công Thức - nói dối e blog

Ghi Chú Phát Triển (27): Máy Tính Công Thức

Kỳ hạn quan trọng thứ hai của dự án chúng tôi đã kết thúc trước Tết Trung Thu. Trong vòng ba tháng phát triển vừa qua, đội ngũ đã tích luỹ thêm nhiều tính năng mới cho game. Tuy nhiên do tiến độ gấp gáp, phần lớn nhiệm vụ phải hoàn thành trong áp lực cao, dẫn đến nhiều lỗi phát sinh.

Sau kỳ nghỉ, chúng tôi quyết định dành một tháng tập trung cải thiện code. Hiện tổng lượng code chưa quá lớn - toàn bộ server code ngoài phần C nền tảng đều viết bằng Lua với khoảng 50 nghìn dòng. Quy mô này hoàn toàn kiểm soát được. Ngay cả khi phải viết lại hoàn toàn từ đầu, dựa trên kinh nghiệm hiện có, cũng không phải điều bất khả thi. Do đó tôi khá tự tin vào việc nâng cấp hệ thống code này.

Một số đoạn code gặp vấn đề hiệu năng nghiêm trọng. Trước Tết, tôi đã thêm các module thống kê và theo dõi, phát hiện ra một số logic xử lý tin nhắn cần đến 10 nghìn lệnh Lua mỗi lần. Đặc biệt nghiêm trọng là các module AI, quản lý quái vật, cùng hệ thống buff và tính toán thuộc tính chiến đấu.

Tôi sẽ lần lượt giải quyết các vấn đề này, bắt đầu từ tối ưu hoá hệ thống tính toán thuộc tính. Cách đây nửa năm, tôi từng tham gia thiết kế và xây dựng module này (xem lại ghi chú phát triển liên quan). Sau khi hoàn thành, tôi chuyển sang công việc khác, các thành viên tiếp nhận đã không dùng lại thiết kế cũ mà chọn giải pháp gián tiếp và dễ cấu hình hơn do yêu cầu từ策划 (bộ phận策划) phức tạp hơn mong đợi. Hiện tại, Mike đã xây dựng hệ thống tương đương hoàn toàn bằng Lua.

Hiện tại gần 200 thuộc tính chiến đấu có mối liên hệ phụ thuộc lẫn nhau, khiến Lua phải gánh vác tính toán nặng nề. Dù vậy vẫn có tin vui - nhiều yêu cầu ban đầu đã không được sử dụng. Ví dụ, ở cấp độ tính toán thuộc tính,策划 không yêu cầu tra bảng (việc này xảy ra ở tầng xử lý buff phía trên). Tất cả thuộc tính cần tính toán đều dùng kiểu float, chỉ cần phép tính cơ bản, không cần chức năng phức tạp.

Tôi quyết định đóng gói toàn bộ logic này vào module C. Giải pháp tối ưu là xây dựng một máy tính hàm thuần tuý dựa trên mô hình thanh ghi dành cho Lua. Lớp trên chỉ cần tạo đối tượng nhóm biểu thức, đưa vào các biểu thức số học đơn giản. Module sẽ tự động phân tích thứ tự phụ thuộc và xác định thanh ghi cần dùng. Khi cập nhật giá trị thanh ghi qua giao diện đơn giản, hệ thống sẽ tự động thực hiện các phép tính cần thiết để sinh ra các thuộc tính phụ thuộc.

Trong module C, biến trong công thức chỉ là dãy số hiệu thanh ghi. Việc chuyển đổi tên thuộc tính có thể đọc được thành mã nội bộ hoàn toàn là công việc thay thế văn bản đơn giản do Lua đảm nhiệm.

Đòi hỏi kỹ thuật rất rõ ràng, việc triển khai cũng không phức tạp. Không cần dùng đến thư viện nặng ký như tcc. Chỉ cần chuyển các biểu thức của策划 thành dạng nghịch Ba Lan (Reverse Polish Notation) tiêu chuẩn. Khi tính toán, chỉ cần quét một lần là có kết quả.

Mẹo thiết kế nhỏ trong cấu trúc dữ liệu: Trong công thức chỉ lưu các hằng số float dương (số âm được biểu diễn qua phép toán phủ định), dùng union để lưu thanh ghi hoặc toán tử. Nhờ đó mọi biểu thức nghịch Ba Lan đều biểu diễn dưới dạng mảng float.

Theo truyền thống, tôi đã open source module hoàn thành trong một ngày này trên GitHub. Những ai quan tâm có thể tìm thấy source code tại đây. Không cần giải thích nhiều, nếu chạy đoạn code sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
local attrib = require "attrib"
local e = attrib.expression {
  "Công kích = Sức mạnh * 10",
  "HP = (Sức bền +10) *2",
  "FOO = Công kích + HP",
}
local a = attrib.new(e)
a["Sức mạnh"] = 3
a["Sức bền"] = 4
for k,v in pairs(a) do
  print(k,v)
end

Kết quả xuất ra sẽ là:

1
2
3
4
5
Sức mạnh  3
Công kích  30
FOO 58
Sức bền  4
HP 28

Sau khi hoàn thành module, Mike đề xuất thêm một tính năng thường dùng trong game. Cụ thể, giá trị “Sức mạnh” thường có giá trị gốc, rồi được cộng thêm từ trang bị, buff theo công thức:

1
2
Sức mạnh = ( Sức mạnh gốc + Tăng thêm ) * (1 + Tỷ lệ tăng)
if ( tồn tại Sức mạnh tuyệt đối ) then Sức mạnh = Sức mạnh tuyệt đối end

Tương tự cho các thuộc tính khác như Trí lực, Nhanh nhẹn. Tôi cho rằng nên để Lua tầng trên xử lý yêu cầu này bằng cách tự động sinh các thuộc tính trung gian như “Tăng thêm Sức mạnh” và cung cấp giao diện thay đổi tương ứng. Riêng trường hợp “Sức mạnh tuyệt đối”, có thể dùng mẹo nhỏ: tạo biến “Cài đặt Sức mạnh” (1/0), từ đó xây dựng công thức:

1
2
Công thức Sức mạnh = ( Sức mạnh gốc + Tăng thêm ) * (1 + Tỷ lệ tăng)
Sức mạnh = Công thức Sức mạnh*(1-Cài đặt) + Sức mạnh tuyệt đối*Cài đặt

Giải pháp này đảm bảo không làm phức tạp thêm module hiện tại, đồng thời giải quyết trọn vẹn yêu cầu phát sinh một cách linh hoạt.

0%