Bài viết kỹ thuật

Thư viện Thành phần Alcinoe và Khả năng Tương thích với Delphi 11.1 Alexandria

Alcinoe là một thư viện thành phần mã nguồn mở dành cho Delphi và C++Builder, được duy trì trên GitHub bởi Zeus64. Nó bao gồm những phần mà VCL và FireMonkey RTL để lại cho bên thứ ba: một trình phát video có tăng tốc GPU, một wrapper WebRTC, các điều khiển chỉnh sửa gốc cho iOS và Android, một bộ phân tích cú pháp JSON/BSON chế độ kép, một ứng dụng khách MongoDB có hỗ trợ gom nhóm kết nối (connection pooling), một wrapper ImageMagick, và một bộ sưu tập các điều khiển FireMonkey hoàn toàn bỏ qua đường ống kết xuất (rendering pipeline) mặc định. Thư viện đã tạo dựng được danh tiếng trên Rio (10.3.3) và Sydney (10.4.2), và kể từ đó đã theo sát từng bản phát hành của Embarcadero. Cho đến thời điểm viết bài, nó hoàn toàn tương thích với Delphi 11.1 Alexandria và Delphi Athens 12.3

Đưa Alcinoe vào một dự án

Việc cài đặt phụ thuộc vào một câu hỏi: bạn có cần hỗ trợ thời gian thiết kế (design-time support) cho các điều khiển trực quan của Alcinoe không? Nếu không, hãy bỏ qua BPL hoàn toàn. Thêm {alcinoe_rootdir}\source vào đường dẫn tìm kiếm thư viện (library search path) của dự án và bạn đã hoàn tất. Mọi thành phần không trực quan, bao gồm bộ phân tích cú pháp, ứng dụng khách cơ sở dữ liệu và tiện ích chuỗi, đều được biên dịch từ mã nguồn mà không cần đăng ký bất kỳ thứ gì

Khi bạn cần hỗ trợ thời gian thiết kế, quá trình sẽ dài hơn một chút. Mở Component > Install Packages trong IDE Delphi, duyệt đến tệp BPL phù hợp với phiên bản của bạn (ví dụ: {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl), cài đặt nó, và sau đó vẫn thêm {alcinoe_rootdir}\source vào đường dẫn tìm kiếm. Tệp BPL sẽ đăng ký các thành phần; thư mục nguồn là nơi trình biên dịch sẽ tìm thấy khi nó biên dịch dự án của bạn

Alcinoe cung cấp các bản vá tùy chọn cho các nguồn RTL của Embarcadero. Nếu bạn muốn sử dụng chúng, hãy điều hướng đến {alcinoe_rootdir}\embarcadero\, chọn thư mục con cho phiên bản của bạn và chạy update.bat. Kịch bản này yêu cầu GIT trong PATH và giả định vị trí cài đặt mặc định của Embarcadero. Nó sẽ lấy mã nguồn RTL gốc và áp dụng các bản vá. Khi hoàn tất, hãy thêm thư mục nguồn đã được vá đó vào đường dẫn tìm kiếm của dự án để trình biên dịch chọn nó trước bản sao chỉ đọc trong cây cài đặt Embarcadero. Không có điều nào trong số này là bắt buộc để bắt đầu; nó chỉ quan trọng nếu bạn gặp phải các lỗi mà các bản vá giải quyết

Android và proxy D8 desugaring

Một số thành phần của Alcinoe (WebRTC, video được hỗ trợ bởi ExoPlayer) phụ thuộc vào các thư viện Java sử dụng các tính năng ngôn ngữ Java 8. Chuỗi công cụ Android đi kèm với các phiên bản Delphi cũ hơn sử dụng dx.bat để chuyển đổi DEX, nhưng không thể xử lý các bytecode đó trên API cấp thấp hơn 26. Cách khắc phục là desugaring, mà D8 xử lý tự động khi được gọi trực tiếp. Alcinoe cung cấp một proxy script tại {alcinoe_rootdir}\tools\D8Proxy\dx.bat để chuyển tiếp các lệnh gọi từ hệ thống biên dịch Delphi đến D8, làm cho quá trình desugaring trở nên vô hình. Thay thế dx.bat gốc trong thư mục build-tools của SDK Android của bạn (thường là C:\SDKs\android\build-tools\30.0.3\) bằng proxy này. Embarcadero đã theo dõi vấn đề cốt lõi tại RSP-24155; các phiên bản sau của công cụ SDK đã giải quyết vấn đề này trực tiếp, vì vậy hãy kiểm tra xem chuỗi công cụ hiện tại của bạn có còn cần cách giải quyết này hay không

Vấn đề kết xuất FireMonkey và câu trả lời của Alcinoe

Chu kỳ vẽ (paint cycle) mặc định của FireMonkey trở thành một nút thắt cổ chai trong các giao diện người dùng cuộn (scroll-heavy UIs). Một TRectangle duy nhất với các góc bo tròn có thể mất khoảng 3 mili-giây để vẽ lại vì quá trình triển khai gốc tính toán lại đường dẫn (path) trên mỗi khung hình. Với 20 điều khiển như vậy hiển thị, sẽ mất tới 60 mili-giây cho mỗi lần vẽ khung hình, điều này sẽ giới hạn tốc độ khung hình hiệu quả thấp hơn nhiều so với ngưỡng cần thiết để cuộn mượt mà

Alcinoe giải quyết vấn đề này với một bộ đệm trên GPU cho mỗi điều khiển. Lần vẽ đầu tiên kết xuất điều khiển thành một TTexture được lưu trữ trong bộ nhớ GPU. Các lần vẽ lại sau đó sẽ blit kết cấu đó thay vì thực thi lại thuật toán vẽ. Kết quả đo được trên cùng một hình chữ nhật bo tròn giảm từ khoảng 3 mili-giây xuống còn khoảng 0.1 mili-giây. Ngoài việc đệm, Alcinoe thay thế quá trình vẽ đường dẫn OpenGL cho các hình dạng cơ bản bằng các API vẽ gốc của Android và iOS, tránh khỏi sự đánh đổi chất lượng/hiệu suất gắn với Form.Quality. Các điều khiển liên quan là TALRectangle, TALCircle, và một tập hợp các vùng chứa bố cục được cải tiến bao gồm ScrollBoxTabControl

TALJsonDocument: DOM và SAX trong một kiểu

TALJsonDocument là bộ phân tích cú pháp JSON và BSON của Alcinoe. Nó hỗ trợ hai chế độ duyệt (traversal modes). Chế độ DOM xây dựng một cây đối tượng trong bộ nhớ, cho phép truy cập ngẫu nhiên vào bất kỳ nút nào với chi phí bộ nhớ tỷ lệ thuận với kích thước tài liệu. Chế độ SAX kích hoạt các sự kiện khi bộ phân tích cú pháp đọc từng mã thông báo mà không giữ lại bất kỳ cây nào, đây là lựa chọn đúng đắn khi bạn cần lọc một tài liệu lớn và chỉ giữ một vài giá trị. Các bộ phân tích cú pháp DOM trong Delphi (DBXJSON, SuperObject, và các bộ khác) thường chậm hơn từ ba đến năm lần so với cách tiếp cận SAX cho cùng nội dung, bởi vì mỗi phân bổ nút mang theo chi phí tạo đối tượng ngoài công việc phân tích cú pháp

Kiểu này tuân theo mẫu điều hướng nút giống như TALXMLDocument. Cú pháp đọc DOM tối thiểu trông như thế này:

MyJsonDoc.LoadFromJSON(AJsonStr, False {dom mode});
MyJsonDoc.ParseOptions := [poAllowComments];

// read scalar values
ShowMessage(MyJsonDoc.ChildNodes['name'].ChildNodes['first'].Text);
ShowMessage(IntToStr(MyJsonDoc.ChildNodes['_id'].Int32));

// iterate an array
for I := 0 to MyJsonDoc.ChildNodes['contribs'].ChildNodes.Count - 1 do
  Writeln(MyJsonDoc.ChildNodes['contribs'].ChildNodes[I].Text);

Đối với chế độ SAX, hãy gán một thủ tục ẩn danh cho OnParseText trước khi gọi LoadFromJSON với tham số thứ hai được đặt thành True. Gọi lại nhận đường dẫn nút, tên, giá trị, và một TALJSONNodeSubType xác định kiểu JSON (string, integer, float, boolean, v.v.). Chế độ đó không tạo ra các phân bổ heap cho các nút, do đó, nó có thể mở rộng tới các tài liệu lớn tùy ý mà không làm tràn bộ nhớ

TALJsonDocument cũng đọc và ghi BSON một cách nguyên bản; hãy truyền cờ True làm cờ BSON cho LoadFromFile hoặc SaveToFile. Một biến thể thứ hai, TALJsonDocumentU, sử dụng UnicodeString (UTF-16) nội bộ thay vì AnsiString (UTF-8) cho các ngữ cảnh nơi đoạn mã xung quanh làm việc xuyên suốt bằng Unicode

Khách hàng MongoDB và gom nhóm kết nối

Trình điều khiển MongoDB của Alcinoe bao gồm các hoạt động truy vấn phổ biến và xử lý gom nhóm kết nối gốc. Ứng dụng khách đơn giản, TAlMongoDBClient, mở và đóng một kết nối duy nhất cho mỗi thao tác. Biến thể được gom nhóm, TAlMongoDBConnectionPoolClient, duy trì một tập hợp các kết nối đang hoạt động và giao một kết nối cho mỗi chuỗi đang gọi từ nhóm, trả nó về khi cuộc gọi hoàn tất. Mô hình đó giữ cho nhiều luồng (threads) không chặn lẫn nhau trong thiết lập kết nối, điều này quan trọng bất cứ khi nào các công nhân nền (background workers) truy vấn cùng một cơ sở dữ liệu đồng thời. Đối với các con trỏ tailable trên các tập hợp được giới hạn (capped collections), TAlMongoDBTailMonitoringThread theo dõi các tài liệu mới và kích hoạt một lệnh gọi lại khi chúng đến, đây là một mẫu tiêu chuẩn để truyền phát nhật ký hoặc thông báo thay đổi mà không cần bỏ phiếu

Các thành phần khác đáng biết

ALVideoPlayer hiển thị video thành TTexture thay vì một cửa sổ lớp phủ (overlay window), vì vậy các điều khiển FireMonkey khác có thể nằm bên trên nó theo thứ tự Z. Phần phụ trợ Android sử dụng ExoPlayer, hỗ trợ thêm DASH, HLS và SmoothStreaming vượt xa những gì MediaPlayer tích hợp sẵn của Android xử lý. Phần phụ trợ iOS sử dụng AVPlayer với hỗ trợ HLS tương đương

TALWebRTC bọc ngăn xếp WebRTC (WebRTC stack) cho âm thanh và video ngang hàng (peer-to-peer). Nó không yêu cầu trình duyệt hoặc plugin, và kết nối đi qua NAT thông qua đàm phán ICE/STUN/TURN tiêu chuẩn mà thư viện cơ sở xử lý

TALStringList thay thế việc sắp xếp dựa trên AnsiCompareText của TStringList bằng tính năng so sánh thứ tự độc lập với ngôn ngữ (locale-independent) và tính năng sắp xếp nhanh (quicksort) nhanh hơn gấp 10 lần trên các danh sách lớn. Biến thể được băm, TALHashedStringList, thêm một bảng băm bên trong để tra cứu O(1) với chi phí tăng cao hơn một chút đối với các danh sách nhỏ. Lưu ý rằng TALStringList là một danh sách AnsiString 8 bit, không phải danh sách Unicode; nó rất phù hợp cho mã phía máy chủ, nơi UTF-8 là định dạng mã hóa đang hoạt động và thông lượng gốc quan trọng hơn việc so sánh chú ý đến ngôn ngữ (locale-aware)

Trên Windows 64-bit, di sản FastCode mang lại lợi thế về tốc độ cho nhiều thuật toán chuỗi của Alcinoe (chủ yếu là ngôn ngữ hợp ngữ x86 viết tay) không được chuyển giao. Các bản dựng Win64 chuyển lại sang các triển khai Pascal, chạy chậm hơn rõ rệt trên các khối lượng công việc dùng nhiều chuỗi. Dự án demo\ALStringBenchMark cho phép bạn đo lường khoảng cách trên phần cứng của mình trước khi cam kết với bản dựng 64-bit mà thông lượng chuỗi là nút thắt cổ chai

Toàn bộ mã nguồn có tại github.com/Zeus64/alcinoe