Làm cách nào tôi có thể hỗ trợ giao tiếp giữa các thành phần một cách an toàn và với bộ lưu trữ thành phần thân thiện với bộ đệm?


9

Tôi đang tạo một trò chơi sử dụng các đối tượng trò chơi dựa trên thành phần và tôi đang gặp khó khăn khi thực hiện một cách để mỗi thành phần giao tiếp với đối tượng trò chơi của nó. Thay vì giải thích mọi thứ cùng một lúc, tôi sẽ giải thích từng phần của mã mẫu có liên quan:

class GameObjectManager {
    public:
        //Updates all the game objects
        void update(Time dt);

        //Sends a message to all game objects
        void sendMessage(Message m);

    private:
        //Vector of all the game objects
        std::vector<GameObject> gameObjects;

        //vectors of the different types of components
        std::vector<InputComponent> input;
        std::vector<PhysicsComponent> ai;
        ...
        std::vector<RenderComponent> render;
}

Việc GameObjectManagergiữ tất cả các đối tượng trò chơi và các thành phần của chúng. Nó cũng chịu trách nhiệm cập nhật các đối tượng trò chơi. Nó thực hiện điều này bằng cách cập nhật các vectơ thành phần theo một thứ tự cụ thể. Tôi sử dụng các vectơ thay vì các mảng để hầu như không có giới hạn về số lượng đối tượng trò chơi có thể tồn tại cùng một lúc.

class GameObject {
    public:
        //Sends a message to the components in this game object
        void sendMessage(Message m);

    private:
        //id to keep track of components in the manager
        const int id;

        //Pointers to components in the game object manager
        std::vector<Component*> components;
}

Các GameObjectlớp biết những gì các thành phần của nó đang có và có thể gửi tin nhắn cho họ.

class Component {
    public:
        //Receives messages and acts accordingly
        virtual void handleMessage(Message m) = 0;

        virtual void update(Time dt) = 0;

    protected:
        //Calls GameObject's sendMessage
        void sendMessageToObject(Message m);

        //Calls GameObjectManager's sendMessage
        void sendMessageToWorld(Message m);
}

Các Componentlớp học là tinh khiết để ảo mà lớp dành cho các loại khác nhau của các thành phần có thể thực hiện như thế nào để xử lý tin nhắn và cập nhật. Nó cũng có thể gửi tin nhắn.

Bây giờ vấn đề phát sinh về cách các thành phần có thể gọi các sendMessagehàm trong GameObjectGameObjectManager. Tôi đã đưa ra hai giải pháp khả thi:

  1. Đưa ra Componentmột con trỏ tới nó GameObject.

Tuy nhiên, vì các đối tượng trò chơi nằm trong một vectơ, các con trỏ có thể nhanh chóng bị vô hiệu hóa (Điều tương tự có thể được nói về vectơ trong GameObject, nhưng hy vọng giải pháp cho vấn đề này cũng có thể giải quyết vấn đề đó). Tôi có thể đặt các đối tượng trò chơi trong một mảng, nhưng sau đó tôi sẽ phải chuyển một số tùy ý cho kích thước, có thể dễ dàng gây ra bộ nhớ cao và lãng phí một cách không cần thiết.

  1. Đưa ra Componentmột con trỏ tới GameObjectManager.

Tuy nhiên, tôi không muốn các thành phần có thể gọi chức năng cập nhật của người quản lý. Tôi là người duy nhất làm việc trong dự án này, nhưng tôi không muốn có thói quen viết mã nguy hiểm tiềm tàng.

Làm cách nào để giải quyết vấn đề này trong khi giữ cho mã của tôi an toàn và thân thiện với bộ đệm?

Câu trả lời:


6

Mô hình truyền thông của bạn có vẻ ổn và tùy chọn một sẽ hoạt động tốt nếu chỉ bạn có thể lưu trữ những con trỏ đó một cách an toàn. Bạn có thể giải quyết vấn đề đó bằng cách chọn một cấu trúc dữ liệu khác để lưu trữ thành phần.

A std::vector<T>là một lựa chọn đầu tiên hợp lý. Tuy nhiên, hành vi vô hiệu hóa trình lặp của bộ chứa là một vấn đề. Những gì bạn muốn là một cấu trúc dữ liệu nhanh và kết hợp bộ nhớ cache để lặp lại, cũng duy trì sự ổn định của trình lặp khi chèn hoặc xóa các mục.

Bạn có thể xây dựng một cấu trúc dữ liệu như vậy. Nó bao gồm một danh sách các trang được liên kết . Mỗi trang có một dung lượng cố định và giữ tất cả các mục trong một mảng. Một số được sử dụng để chỉ ra có bao nhiêu mục trong mảng đó đang hoạt động. Một trang cũng có một danh sách miễn phí (cho phép sử dụng lại các mục đã xóa) và danh sách bỏ qua (cho phép bạn bỏ qua các mục đã bị xóa trong khi lặp lại.

Nói cách khác, về mặt khái niệm, một cái gì đó như:

struct Page {
   int count;
   int capacity;           // Optional if every page is a fixed size.
   T * m_storage;
   bool * m_skip;          // Skip list; can be bit-compressed.
   std::stack<int> m_free; // Can be replaced with a specialized stack.

   Page * next;
   Page * prior;           // Optional, allows reverse iteration
};

Tôi thường gọi cấu trúc dữ liệu này là một cuốn sách (bởi vì nó là một tập hợp các trang bạn lặp đi lặp lại), nhưng cấu trúc này có nhiều tên khác.

Matthew Bentley gọi đó là "thuộc địa". Việc triển khai của Matthew sử dụng trường bỏ qua bước nhảy (xin lỗi cho liên kết MediaFire, nhưng đó là cách chính Bentley lưu trữ tài liệu), vượt trội hơn so với danh sách bỏ qua dựa trên boolean điển hình trong các loại cấu trúc này. Thư viện của Bentley chỉ dành cho tiêu đề và dễ dàng đưa vào bất kỳ dự án C ++ nào, vì vậy tôi khuyên bạn chỉ nên sử dụng nó so với việc tự mình thực hiện. Có rất nhiều sự tinh tế và tối ưu hóa mà tôi đang đề cập ở đây.

Bởi vì cấu trúc dữ liệu này không bao giờ di chuyển các mục sau khi chúng được thêm vào, các con trỏ và các trình lặp cho mục đó vẫn còn hiệu lực cho đến khi chính mục đó bị xóa (hoặc chính vùng chứa bị xóa). Bởi vì nó lưu trữ các khối của các mục được phân bổ liên tục, việc lặp lại rất nhanh và chủ yếu là kết hợp bộ nhớ cache. Chèn và loại bỏ đều hợp lý.

Nó không hoàn hảo; có thể phá hỏng sự kết hợp bộ đệm với mô hình sử dụng bao gồm xóa rất nhiều từ các điểm ngẫu nhiên có hiệu quả trong vùng chứa và sau đó lặp lại trên vùng chứa đó trước khi các lần chèn tiếp theo có các mục được lấp đầy. Nếu bạn thường xuyên ở trong kịch bản đó, bạn sẽ bỏ qua các vùng bộ nhớ có khả năng lớn tại một thời điểm. Tuy nhiên trong thực tế tôi nghĩ rằng container này là một lựa chọn hợp lý cho kịch bản của bạn.

Các cách tiếp cận khác mà tôi sẽ đưa ra cho các câu trả lời khác để trình bày, có thể bao gồm cách tiếp cận dựa trên tay cầm hoặc loại cấu trúc bản đồ vị trí (trong đó bạn có một mảng kết hợp của các "số nguyên" với các giá trị "số nguyên", các giá trị là các chỉ số trong một mảng sao lưu, cho phép bạn lặp lại qua một vectơ bằng cách vẫn truy cập bằng "chỉ mục" với một số chỉ dẫn bổ sung).


Chào! Có tài nguyên nào để tôi có thể tìm hiểu thêm về các lựa chọn thay thế cho "thuộc địa" mà bạn đã đề cập trong đoạn cuối không? Họ có thực hiện ở bất cứ đâu không? Tôi đã nghiên cứu chủ đề này một thời gian và tôi thực sự quan tâm.
Rinat Veliakhmedov

5

Trở nên 'thân thiện với bộ nhớ cache' là một trò chơi lớn đang bận tâm . Điều này dường như là tối ưu hóa sớm với tôi.


Một cách để giải quyết vấn đề này mà không phải là 'thân thiện với bộ đệm' sẽ là tạo đối tượng của bạn trên heap thay vì trên ngăn xếp: sử dụng newvà con trỏ (thông minh) cho các đối tượng của bạn. Bằng cách này, bạn sẽ có thể tham chiếu các đối tượng của mình và tham chiếu của chúng sẽ không bị vô hiệu.

Để có giải pháp thân thiện với bộ đệm hơn, bạn có thể tự mình quản lý khử / phân bổ các đối tượng và sử dụng tay cầm cho các đối tượng này.

Về cơ bản, khi khởi tạo chương trình của bạn, một đối tượng dự trữ một phần bộ nhớ trên heap (hãy gọi nó là MemMan), sau đó, khi bạn muốn tạo một thành phần, bạn nói với MemMan rằng bạn cần một thành phần có kích thước X, nó ' sẽ dành nó cho bạn, tạo một tay cầm và giữ bên trong nơi phân bổ của nó là đối tượng cho tay cầm đó. Nó sẽ trả về tay cầm và điều duy nhất bạn sẽ giữ về đối tượng, không bao giờ là con trỏ đến vị trí của nó trong bộ nhớ.

Khi bạn cần thành phần, bạn sẽ yêu cầu MemMan truy cập vào đối tượng này, điều mà nó sẽ sẵn sàng làm. Nhưng đừng giữ tham chiếu đến nó bởi vì ....

Một trong những công việc của MemMan là giữ các đối tượng gần nhau trong bộ nhớ. Cứ sau vài khung hình trò chơi, bạn có thể yêu cầu MemMan sắp xếp lại các đối tượng trong bộ nhớ (hoặc nó có thể tự động thực hiện khi bạn tạo / xóa các đối tượng). Nó sẽ cập nhật bản đồ vị trí xử lý vào bộ nhớ. Tay cầm của bạn sẽ luôn hợp lệ, nhưng nếu bạn giữ một tham chiếu đến không gian bộ nhớ (một con trỏ hoặc một tham chiếu ), bạn sẽ chỉ thấy sự tuyệt vọng và hoang vắng.

Sách giáo khoa nói rằng cách quản lý bộ nhớ của bạn có ít nhất 2 ưu điểm:

  1. ít bộ nhớ cache hơn vì các đối tượng gần nhau trong bộ nhớ và
  2. nó làm giảm số lượng cuộc gọi giảm / phân bổ bộ nhớ mà bạn sẽ thực hiện với HĐH, được cho là sẽ mất một thời gian.

Hãy nhớ rằng cách bạn sử dụng MemMan và cách bạn sắp xếp bộ nhớ trong thực sự phụ thuộc vào cách bạn sẽ sử dụng các thành phần của mình. Nếu bạn lặp lại thông qua chúng dựa trên loại của chúng, bạn sẽ muốn giữ các thành phần theo loại, nếu bạn lặp qua chúng dựa trên đối tượng trò chơi của chúng, bạn sẽ cần tìm cách đảm bảo chúng gần với khác dựa trên điều đó, vv ...

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.