Một Công Cụ Để Định Dạng Thông Tin Văn Bản - nói dối e blog

Một Công Cụ Để Định Dạng Thông Tin Văn Bản

Một công cụ nhỏ để định dạng giao diện thông tin văn bản

Thư viện bgfx cung cấp một bộ API để xuất thông tin gỡ lỗi dưới dạng văn bản, cho phép hiển thị các thông điệp lên màn hình. Tuy nhiên những API này còn rất sơ khai, chỉ đơn thuần mô phỏng bộ đệm chế độ văn bản. Để tiến gần hơn tới chức năng của một giao diện console thì vẫn còn nhiều hạn chế.

Chi tiết có thể xem trong tài liệu các hàm thuộc nhóm dbgText*. Khi số lượng thông tin cần hiển thị trong engine game ngày càng tăng, việc sử dụng trực tiếp các API này càng bộc lộ nhiều điểm bất tiện. Gần đây tôi nảy ra ý định dùng imgui để xây dựng giao diện hiển thị thông tin gỡ lỗi. Tuy nhiên tôi vẫn thấy tiếc nuối những tiện ích mà chế độ văn bản tích hợp sẵn trong bgfx mang lại.

Vào cuối tuần vừa rồi, con cái được ông bà đưa về quê. Đây là dịp hiếm hoi tôi có một ngày rảnh rang không phải chăm sóc con nhỏ. Sáng thứ Bảy vừa thức dậy sớm tôi đã háo hức muốn bắt tay vào viết code.

Ý tưởng ban đầu là sử dụng các thư viện UI dựa trên ncursors. Sau khi tìm kiếm qua một vòng, tôi không thấy có thư viện nào thật sự ưng ý. Ngoài ra, chế độ văn bản của bgfx không phải là một terminal đầy đủ, có lẽ cần phải cải tiến nó thành một terminal tương thích VT100 trước đã.

Khi tìm kiếm trên GitHub với từ khóa “VT100 Emulator”, tôi phát hiện vài thư viện đơn giản nhưng chưa có giải pháp plug-and-play. Tự tay viết một bộ mô phỏng VT100 dường như không quá phức tạp, ước chừng khoảng 400 dòng mã là đủ. Việc chính là triển khai hỗ trợ các mã ANSI escape, khi đó có thể kết nối với các thư viện như ncursors hay pdcursors để mở rộng khả năng tạo giao diện văn bản (TUI) với nhiều lựa chọn phong phú hơn.

Tình cờ tôi phát hiện một dự án thú vị có tên imtui - một backend văn bản cho imgui. Trông nó khá ấn tượng nhưng khi xem xét kỹ thì dự án đã lâu không cập nhật. Qua nghiên cứu mã nguồn chỉ vài trăm dòng, tôi nắm bắt được nguyên lý trong vòng nửa tiếng đồng hồ.

Imgui tạo ra một danh sách lệnh vẽ (draw list) chứa các lệnh đồ họa. Backend có nhiệm vụ chuyển các lệnh này sang API đồ họa cụ thể để hiển thị. Tuy nhiên danh sách này chỉ chứa luồng dữ liệu đỉnh (vertex stream) mà không giữ lại thông tin ngữ nghĩa về đối tượng ban đầu cần vẽ.

Ví dụ, chữ viết trên giao diện ở dạng draw list sẽ chỉ hiện lên như hai tam giác ghép lại. Các mũi tên menu biến thành những tam giác đơn lẻ. Hình tròn lại được chia thành hàng loạt tam giác nhỏ… Nếu muốn tái tạo các hình dạng này trong chế độ văn bản mà không sửa đổi mã nguồn imgui, chúng ta chỉ còn cách “đoán” nội dung từ các tam giác này. Điều này đòi hỏi phải phân tích luồng đỉnh để khôi phục lại lệnh vẽ ở cấp cao hơn. Thư viện imtui làm chưa thật sự tốt phần này, tôi nghĩ ra vài phương án đoán thông minh và hiệu quả hơn nhiều.

Trong buổi sáng hôm đó, tôi quyết định tự viết một backend imgui mới theo hướng tiếp cận riêng. Tôi định nghĩa một ID texture đặc biệt dành cho font chữ. Bất cứ khi nào draw list sử dụng ID này, tôi xác định đó là lệnh vẽ văn bản. Tôi dùng font chữ ASCII tùy chỉnh, thay thế toàn bộ ký tự imgui bằng các ký tự 1x1 pixel đơn giản, đồng thời ghi các ký tự ASCII lên texture. Khi phát hiện lệnh vẽ chữ, tôi chỉ cần lấy mã ASCII tương ứng thông qua giá trị UV trên texture font.

Với các cặp tam giác liền kề có thể ghép thành hình chữ nhật, tôi xác định đó là lệnh vẽ khung nền. Việc hiển thị các khung màu trên bộ đệm văn bản rất dễ thực hiện. Nhân tiện nói thêm, imtui có dùng mã raster hóa tam giác nhưng tôi thấy hoàn toàn không cần thiết. Các tam giác không phải hình chữ nhật khác thường dễ dàng nhận diện thành các mũi tên chỉ hướng, có thể chuyển đổi thành ký tự văn bản tương ứng. Các hình dạng phức tạp khác sẽ bị bỏ qua.

Khi hoàn thành bản thử nghiệm backend imgui mới và kiểm chứng khả năng hiển thị văn bản toàn màn hình, tôi bỗng nghi ngờ lại hướng tiếp cận này. Việc kết nối với API bgfx không khó, nhưng liệu có ý nghĩa gì khi bắt imgui vẽ ra hàng loạt tam giác rồi lại vất vả giải mã ngược lại?

Thực tế tôi đâu cần một giao diện UI tương tác đầy đủ. Mục đích chỉ đơn giản là hiển thị thông tin gỡ lỗi trên màn hình. Khó dùng trước đây vì thiếu giao diện bố cục linh hoạt. Tôi từng dùng imgui một thời gian, rất thích API bảng biểu của nó cho phép chia vùng màn hình tùy ý và đặt các thành phần điều khiển bên trong.

Nếu chỉ cần mô-đun bố cục màn hình như vậy và chỉ hỗ trợ xuất văn bản, vấn đề sẽ được giải quyết triệt để. Phân tích kỹ hơn, việc triển khai không quá phức tạp. Tôi lập tức loại bỏ toàn bộ mã viết buổi sáng, chiều cùng ngày bắt đầu viết lại một thư viện định dạng văn bản mới từ đầu. Phần thiết kế API khó nhất đã được imgui làm sẵn, tôi chỉ cần học hỏi theo. Toàn bộ hiện thực chỉ cần vài trăm dòng mã là xong.

https://github.com/cloudwu/textcell

Tới giờ ăn tối thì cơ bản hoàn thành. Những dự án nhỏ gọn hoàn thành trong một ngày như thế này thật sự là món giải trí tuyệt vời vào cuối tuần.

0%