Các thành phần cố định trong mảng (hệ thống thành phần thực thể)


8

Tôi đang tìm cách cải thiện ECS của mình và tôi đã thấy nhiều người đề nghị lưu trữ các thành phần trong mảng. Điều này có vẻ tuyệt vời, xem xét truy cập nhanh cho một thành phần bằng cách sử dụng id thực thể và quan trọng hơn là lặp lại nhanh các thành phần của mảng (vì các hệ thống làm điều này mọi lúc) và ít bộ nhớ cache hơn so với khi tôi lưu trữ các thành phần trong Thực thể hoặc sử dụng:

std::map<int,std::shared_ptr<Component>> components // where int is a component id 

Nhưng tôi có một số câu hỏi về phương pháp này.

1) Tôi có phải tạo mảng cho từng thành phần không? Cái gì đó như:

PositionComponent positionComponents[MAX_OBJECTS];
HealthComponent healthComponents[MAX_OBJECTS];
...

Hoặc có cách nào tốt hơn để lưu trữ các thành phần khác nhau liên tục trong bộ nhớ?

2) Làm thế nào tôi có thể nhận được các thành phần bằng cách sử dụng id thực thể nếu tôi làm điều này?

Tôi muốn thực hiện một số chức năng như thế này:

template <class T>
T* getComponent(int entityId) const {
    ... // ??
}

vì vậy tôi có thể sử dụng nó như thế này:

PositionComponent* pc = getComponent<PositionComponent>(entityId);

Câu trả lời:


12

Điều này có vẻ tuyệt vời, xem xét truy cập nhanh cho một thành phần bằng cách sử dụng id thực thể và quan trọng hơn là lặp lại nhanh các thành phần của mảng (vì các hệ thống làm điều này mọi lúc) và ít bộ nhớ cache hơn so với khi tôi lưu trữ các thành phần trong Thực thể hoặc sử dụng:

Bạn đã thực sự đo nhớ cache? Có phải họ là một vấn đề đối với bạn? Sẽ loại bỏ chúng cho truy cập thành phần của bạn có thể có tác động có thể đo lường được? Có đáng để bạn dành thời gian tối ưu hóa để lo lắng về chúng thay vì tập trung vào sự song song hoặc sử dụng GPU tốt hơn không? Bạn có ít nhất có các phép đo mã của mình trước bất kỳ thay đổi nào như vậy để bạn có thể so sánh chúng với các phép đo từ sau khi thay đổi không?

Đơn giản chỉ cần đặt mọi thứ vào một mảng cũng không đảm bảo sẽ bỏ lỡ bộ nhớ cache ít hơn. Đây là bước đầu tiên của một số để cải thiện việc sử dụng bộ đệm cho các thành phần. Nó có thể là một bước sai để cải thiện hiệu suất tổng thể của bạn, quá.

1) Tôi có phải tạo mảng cho từng thành phần không? Cái gì đó như:

PositionComponent positionComponents[MAX_OBJECTS];
HealthComponent healthComponents[MAX_OBJECTS];

Một std::vectorhoặc tương tự sẽ làm việc như là tốt. Lưu ý rằng như làvector có thể phân bổ lại bộ lưu trữ sao lưu của nó và làm mất hiệu lực các trình lặp / con trỏ hiện có, bạn sẽ phải sử dụng các chỉ mục thành phần thay vì con trỏ.

Bạn cũng có thể dễ dàng viết một phương tiện lưu trữ chunked (như một std::deque, nhưng không được tối ưu hóa cho hàng đợi) cho phép cấu trúc phát triển, giữ tốc độ lặp nhanh và có con trỏ ổn định.

2) Làm thế nào tôi có thể nhận được các thành phần bằng cách sử dụng id thực thể nếu tôi làm điều này?

template <class T>
T* getComponent(int entityId) const {
    ... // ??
}

Bạn có thể lưu trữ một bảng băm bổ sung boost::flat_maphoặc std::unordered_maphoặc hiệu suất cao tùy chỉnh ánh xạ ID thực thể thành các chỉ số thành phần cho loại thành phần đó.

Có lẽ bạn sẽ chỉ muốn ánh xạ tới một chỉ mục hoặc một con trỏ tạm thời (một con trỏ mà bạn hứa sẽ không bao giờ giữ lại) vì điều đó cho phép chủ sở hữu thành phần chống phân mảnh bộ sưu tập thành phần của nó. Cả hai đều có khả năng cho phép chủ sở hữu giải phóng các khối bộ nhớ và quan trọng hơn (đối với các nhu cầu hướng dữ liệu) là điều cần thiết để thực sự làm cho việc lặp lại các thành phần nhanh hơn. Nó có thể sẽ nhanh hơn nhiều để sử dụng phương pháp tiếp cận hiện tại của bạn so với việc lặp đi lặp lại trên một mảng lớn và dân cư thưa thớt. Hãy nhớ rằng với một mảng thưa thớt (ngay cả khi chiếm 99%), bạn phải thêm một mảng bổ sungif (isInUse) kiểm tra bên trong vòng lặp của bạn qua các thành phần là một lệnh đọc + nhánh bổ sung có thể có tác động tiêu cực đáng chú ý đến tốc độ lặp chung của bạn.

Xem bốn bài viết của BitSquid về chủ đề này . Những kẻ đó có xu hướng tập trung vào mã giống như C và tránh các cách tiếp cận C ++ hiện đại với hiệu suất giống hệt nhau nhưng sử dụng dễ dàng hơn, nhưng các cách tiếp cận cơ bản mà họ sử dụng được áp dụng trong C, C ++, C #, Rust hoặc các ngôn ngữ tương tự.


1
Thành thật mà nói, tôi có nhiều vấn đề hơn với thiết kế và rất nhiều phôi ảo tôi phải làm vào lúc này (tôi lưu trữ các Thành phần trong Thực thể dưới dạng std :: vector <Thành phần *>) so với lỗi bộ nhớ cache. Tôi chỉ muốn tạo ECS "thuần túy" trong đó thực thể chỉ là một id. Và bản đồ không có thứ tự đó sẽ hoạt động như thế nào? Tôi không hiểu lắm. Bạn có thể cung cấp một ví dụ cụ thể hơn, xin vui lòng? Và cảm ơn cho liên kết đó, chắc chắn sẽ kiểm tra nó.
Elias Daler

@EliasDaler: các bài viết được liên kết giải thích việc sử dụng bảng băm như thế nào unordered_map, chỉ có họ sử dụng bảng của riêng họ. Các khái niệm là như nhau.
Sean Middleditch

Thật không may, bài báo của họ không trả lời câu hỏi của tôi. Thiết kế thành phần của họ rất khác với tôi. Tuy nhiên, cảm ơn, tôi sẽ đánh dấu câu trả lời này vì lời giải thích của bạn có ý nghĩa với tôi bây giờ.
Elias Daler

1
@EliasDaler: có lẽ tôi không hiểu câu hỏi. Bài viết này hướng dẫn cách bạn sử dụng bảng băm để ánh xạ ID thực thể đến các chỉ mục thành phần. Bit đó sẽ hoạt động như nhau ngay cả khi các thành phần của bạn khác nhau. Nếu tôi hiểu bạn đúng.
Sean Middleditch

1
@Nikos: đó là cách công nghệ thấp, chắc chắn. Và hoàn toàn đủ cho nhiều người dùng. Có nhiều triển khai tinh vi hơn về ý tưởng chung đó là có thể (dựa trên "bản đồ vị trí" hoặc những gì các bài báo bitquid gọi là "mảng đóng gói" hoặc cái mà EnTT gọi là "bộ thưa thớt") nhưng đừng quá kỹ nếu bạn không có đến. :) Bạn có thể muốn kiểm tra một số bảng băm C ++ mã nguồn mở khác (như các bảng trong Abseil) bên cạnh unordered_mapđó có ... vấn đề (nó được thiết kế cho một tập hợp bất biến không áp dụng ở đây nhưng áp đặt chi phí hoàn hảo nghiêm trọng cho việc này trường hợp).
Sean Middleditch

3

1) Tôi có phải tạo mảng cho từng thành phần không?

Có, nhưng một vectơ sẽ thuận tiện hơn và hiệu suất tương đương.

2) Làm thế nào tôi có thể nhận được các thành phần bằng cách sử dụng id thực thể nếu tôi làm điều này?

Bạn nên lưu trữ ID thực thể trên các thành phần. Giữ vector của bạn được sắp xếp theo ID đó, và sau đó sử dụng vector :: low_bound để truy xuất các thành phần. Trong thử nghiệm của riêng tôi, việc tra cứu này thực sự nhanh hơn bản đồ cho đến một điểm nhất định.

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.