Sử dụng một kiến ​​trúc hệ thống thực thể với tính song song dựa trên nhiệm vụ


9

Lý lịch

Tôi đã làm việc để tạo ra một công cụ trò chơi đa luồng trong thời gian rảnh rỗi và tôi hiện đang cố gắng quyết định cách tốt nhất để làm việc một sytem thực thể vào những gì tôi đã tạo. Cho đến nay, tôi đã sử dụng bài viết này của Intel cho điểm khởi đầu cho động cơ của mình. Cho đến nay tôi đã thực hiện vòng lặp trò chơi thông thường bằng cách sử dụng các tác vụ và bây giờ tôi đang chuyển sang sử dụng một số hệ thống và / hoặc hệ thống thực thể được kết hợp. Tôi đã sử dụng một cái gì đó tương tự như Artemis trong quá khứ, nhưng sự song hành đang ném tôi đi.

Bài viết từ Intel dường như ủng hộ việc có nhiều bản sao dữ liệu thực thể và có những thay đổi được thực hiện cho từng thực thể được phân phối nội bộ vào cuối bản cập nhật hoàn chỉnh. Điều này có nghĩa là kết xuất sẽ luôn ở phía sau một khung, nhưng đó có vẻ như là một sự thỏa hiệp chấp nhận được với các lợi ích hiệu suất cần đạt được. Tuy nhiên, khi nói đến một hệ thống thực thể như Artemis, việc mỗi thực thể được sao chép cho mỗi hệ thống có nghĩa là mỗi thành phần cũng sẽ cần được sao chép. Điều này là có thể làm được nhưng với tôi có vẻ như nó sẽ sử dụng rất nhiều bộ nhớ. Các phần của tài liệu Intel loại bỏ chủ yếu là 2.2 và 3.2.2. Tôi đã thực hiện một số tìm kiếm để xem liệu tôi có thể tìm thấy bất kỳ tài liệu tham khảo tốt nào để tích hợp các kiến ​​trúc tôi sẽ không, nhưng tôi chưa thể tìm thấy bất cứ điều gì hữu ích.

Lưu ý: Tôi đang sử dụng C ++ 11 cho dự án này, nhưng tôi tưởng tượng hầu hết những gì tôi đang hỏi nên là ngôn ngữ bất khả tri.

Giải pháp có thể

Có một EntityManager toàn cầu được sử dụng để tạo và quản lý Entity và EntityAttribution. Chỉ cho phép truy cập đọc vào chúng trong giai đoạn cập nhật và lưu trữ tất cả các thay đổi trong hàng đợi trên mỗi luồng. Khi tất cả các nhiệm vụ được hoàn thành, các hàng đợi được kết hợp và các thay đổi trong từng nhiệm vụ được áp dụng. Điều này có thể có vấn đề với nhiều lần ghi vào cùng một trường nhưng tôi chắc chắn có thể có một hệ thống ưu tiên hoặc dấu thời gian để sắp xếp nó. Đây có vẻ là một cách tiếp cận tốt với tôi vì các hệ thống có thể được thông báo về các thay đổi đối với các thực thể khá tự nhiên trong giai đoạn phân phối thay đổi.

Câu hỏi

Tôi đang tìm kiếm một số thông tin phản hồi về giải pháp của tôi để xem liệu nó có ý nghĩa hay không. Tôi sẽ không nói dối và tự nhận là một chuyên gia về đa luồng và tôi đang làm điều này phần lớn để thực hành. Tôi có thể thấy trước một số mớ hỗn độn phức tạp nảy sinh từ giải pháp của mình, nơi nhiều hệ thống đang đọc / ghi nhiều giá trị. Hàng đợi thay đổi mà tôi đã đề cập cũng có thể khó định dạng theo cách mà mọi thay đổi có thể có thể dễ dàng được truyền đạt khi tôi không làm việc với POD.

Bất kỳ thông tin phản hồi / lời khuyên sẽ được nhiều đánh giá cao! Cảm ơn!

Liên kết

Câu trả lời:


12

Tham gia ngã ba

Bạn không cần các bản sao riêng biệt của các thành phần. Chỉ cần sử dụng một mô hình fork-jo, được đề cập (cực kỳ kém) trong bài viết đó của Intel.

Trong một ECS, bạn thực sự có một vòng lặp giống như:

while in game:
  for each system:
    for each component in system:
      update component

Thay đổi điều này thành một cái gì đó như:

while in game:
  for each system:
    divide components into groups
    for each group:
      start thread (
        for each component in group:
          update component
      )
    wait for all threads to finish

Phần khó khăn là bit "chia các thành phần thành các nhóm". Đối với đồ họa hầu như không cần dữ liệu chia sẻ nên rất đơn giản (chia đều các đối tượng có thể kết xuất theo số lượng luồng công nhân có sẵn). Đối với vật lý và AI, bạn muốn tìm "đảo" logic của các đối tượng không tương tác và đặt chúng lại với nhau. Càng ít tương tác giữa các thành phần, càng tốt.

Đối với sự tương tác phải tồn tại, tin nhắn chậm trễ hoạt động tốt nhất. Nếu đối tượng A cần báo cho đối tượng B nhận sát thương, A chỉ có thể liệt kê một thông điệp vào nhóm trên mỗi luồng. Khi các chủ đề được nối, tất cả các nhóm được nối vào một nhóm duy nhất. Mặc dù không liên quan trực tiếp đến phân luồng, hãy xem loạt bài viết sự kiện từ các nhà phát triển BitSquid (thực tế, đọc toàn bộ blog; tôi không đồng ý với mọi thứ trên đó, nhưng đó là một tài nguyên tuyệt vời).

Lưu ý rằng "fork-jo" không có nghĩa là sử dụng fork()(tạo ra các quy trình, không phải các luồng), cũng không có nghĩa là bạn thực sự phải tham gia các chuỗi. Điều đó chỉ có nghĩa là bạn thực hiện một nhiệm vụ duy nhất, phân chia nó thành các phần nhỏ hơn để xử lý bởi nhóm các luồng công nhân của bạn, và sau đó chờ tất cả các bưu kiện được xử lý.

Proxy

Cách tiếp cận này có thể được sử dụng riêng hoặc kết hợp với phương pháp nối ngã ba để làm cho nhu cầu tách biệt nghiêm ngặt trở nên ít quan trọng hơn.

Bạn có thể thân thiện hơn với các chủ đề tương tác bằng cách sử dụng phương pháp hai lớp đơn giản. Có thực thể "có thẩm quyền" và thực thể "proxy". Các thực thể có thẩm quyền chỉ có thể được sửa đổi từ một chủ đề duy nhất là chủ sở hữu rõ ràng của thực thể có thẩm quyền. Các thực thể proxy không thể được sửa đổi, chỉ đọc. Tại một điểm đồng bộ hóa trong vòng lặp trò chơi, tuyên truyền tất cả các thay đổi từ các thực thể có thẩm quyền đến các proxy tương ứng.

Thay thế "thực thể" bằng "các thành phần" khi thích hợp. Điểm chính là bạn cần nhiều nhất hai bản sao của bất kỳ đối tượng nào và có các điểm "đồng bộ hóa" rõ ràng trong vòng lặp trò chơi của bạn khi bạn có thể sao chép từ cái này sang cái khác trong hầu hết các thiết kế công cụ trò chơi có ren.

Bạn có thể mở rộng proxy để vẫn cho phép (một tập hợp con) các phương thức / thông điệp được sử dụng bằng cách đơn giản là chuyển tất cả những thứ như vậy vào hàng đợi được gửi đến đối tượng có thẩm quyền tiếp theo.

Lưu ý rằng phương pháp proxy là một thiết kế tuyệt vời để có ở mức cao hơn vì nó giúp hỗ trợ mạng siêu dễ dàng.


Tôi đã đọc một số điều về ngã ba tham gia mà bạn đã đề cập trước đây và tôi có ấn tượng rằng trong khi nó cho phép bạn sử dụng một số song song, có những tình huống trong đó một số chủ đề công nhân có thể đang chờ đợi trên một nhóm để kết thúc. Lý tưởng nhất, tôi đang cố gắng tránh tình huống đó. Ý tưởng proxy rất thú vị và hơi giống với những gì tôi đang làm. Một thực thể có EntityAttribution và đó là các hàm bao cho các giá trị thực được lưu trữ bởi thực thể. Vì vậy, các giá trị có thể được đọc từ chúng bất cứ lúc nào nhưng chỉ được đặt tại một số thời điểm nhất định và có thể chứa giá trị proxy trong thuộc tính, đúng không?
Ross chào

1
Có một cơ hội tốt để cố gắng tránh chờ đợi, bạn dành quá nhiều thời gian để phân tích biểu đồ phụ thuộc mà bạn mất thời gian nói chung.
Patrick Hughes

@roflha: yeah bạn có thể đặt proxy ở cấp EntityAttribution. Hoặc tạo một thực thể riêng biệt với một bộ thuộc tính thứ hai. Hoặc chỉ cần bỏ khái niệm thuộc tính hoàn toàn và sử dụng một thiết kế thành phần ít chi tiết hơn.
Sean Middleditch

@SeanMiddleditch Khi tôi nói thuộc tính, về cơ bản tôi đang đề cập đến các thành phần tôi nghĩ. Các thuộc tính không chỉ là các giá trị đơn lẻ như float và chuỗi nếu đó là những gì tôi tạo ra. Thay vào đó, chúng là các lớp chứa thông tin cụ thể như PositionAttribution. Nếu thành phần là tên được chấp nhận cho điều đó thì có lẽ tôi nên thay đổi. Nhưng bạn có đề nghị ủy quyền ở cấp thực thể hơn là cấp thành phần / thuộc tính không?
Ross chào

1
Tôi đề nghị bất cứ điều gì bạn thấy dễ dàng nhất để thực hiện. Chỉ cần nhớ rằng điểm tôi có thể truy vấn proxy mà không cần thực hiện bất kỳ khóa nào, không sử dụng bất kỳ nguyên tử nào và không có bế tắc.
Sean Middleditch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.