Với cách tôi tiếp cận mọi thứ, loại đa luồng miễn phí và tương đối đơn giản để áp dụng trong nhận thức muộn. Nhưng tôi đang nghĩ về dữ liệu đầu tiên. Tôi không biết nếu điều này hoạt động cho tất cả các tên miền nhưng tôi sẽ cố gắng bao quát cách tôi đi về nó.
Vì vậy, trước tiên, tất cả là về loại dữ liệu thô nhất cần thiết cho phần mềm sẽ được xử lý thường xuyên. Nếu đó là một trò chơi có thể là những thứ như mắt lưới, âm thanh, chuyển động, phát hạt, ánh sáng, kết cấu, những thứ thuộc loại này. Và tất nhiên, có rất nhiều điều phải suy nghĩ nếu bạn đi sâu vào chỉ lưới và suy nghĩ về cách chúng nên được thể hiện, nhưng chúng ta sẽ bỏ qua điều đó ngay bây giờ. Ngay bây giờ chúng tôi đang suy nghĩ ở cấp độ kiến trúc rộng nhất.
Và suy nghĩ đầu tiên của tôi là "Làm thế nào để chúng ta thống nhất đại diện cho tất cả những điều này để chúng ta có thể đạt được một mẫu truy cập tương đối thống nhất cho tất cả các loại điều này?" Và suy nghĩ đầu tiên của tôi có thể là lưu trữ mọi loại vật thể trong mảng liền kề của riêng nó với một cách loại danh sách miễn phí để lấy lại các khoảng trống. Và điều đó có xu hướng thống nhất API để chúng ta có thể dễ dàng hơn, giả sử, sử dụng cùng một loại mã để tuần tự hóa các mắt lưới khi chúng ta chiếu sáng và kết cấu, ít nhất là ở nơi và cách các thành phần này được truy cập. Chúng ta càng có thể thống nhất cách mọi thứ được thể hiện, mã càng truy cập vào những thứ đó có xu hướng có hình dạng đồng nhất.
Thật tuyệt. Bây giờ chúng ta cũng có thể chỉ ra những điều này với các chỉ số 32 bit và chỉ chiếm một nửa bộ nhớ của con trỏ 64 bit. Và này, bây giờ chúng ta có thể thiết lập các giao điểm trong thời gian tuyến tính nếu chúng ta có thể liên kết một bitet song song, ví dụ: Chúng ta cũng có thể liên kết dữ liệu với bất kỳ một trong những điều này rất rẻ song song vì chúng ta lập chỉ mục mọi thứ. Oh và bitet đó có thể cung cấp cho chúng ta một tập hợp các chỉ mục được sắp xếp để duyệt theo thứ tự tuần tự cho các mẫu truy cập bộ nhớ được cải thiện, không phải tải lại cùng một dòng bộ đệm trong một vòng lặp. Chúng tôi có thể kiểm tra 64 bit cùng một lúc. Nếu tất cả 64 bit không được đặt, chúng ta có thể bỏ qua hơn 64 phần tử cùng một lúc. Nếu tất cả chúng được đặt, chúng ta có thể xử lý tất cả chúng cùng một lúc. Nếu một số được đặt nhưng không phải tất cả, chúng ta có thể sử dụng các lệnh FFS để nhanh chóng xác định bit nào được đặt.
Nhưng chờ đã, điều đó thật tốn kém nếu chúng ta chỉ muốn liên kết dữ liệu với vài trăm thứ trong số hàng chục ngàn thứ. Vì vậy, hãy sử dụng một mảng thưa thớt thay vào đó, như vậy:
Và này, bây giờ chúng ta đã có mọi thứ được lưu trữ trong các mảng thưa thớt và lập chỉ mục cho chúng, sẽ khá dễ dàng để biến điều này thành một cấu trúc dữ liệu liên tục.
Bây giờ chúng ta có thể viết các hàm giá rẻ hơn không có tác dụng phụ vì chúng không cần sao chép sâu những gì chưa thay đổi.
Và ở đây tôi đã được cung cấp một bảng cheat sau khi tìm hiểu về các công cụ ECS, nhưng bây giờ chúng ta hãy nghĩ về loại chức năng rộng nào sẽ hoạt động trên mỗi loại thành phần. Chúng ta có thể gọi những "hệ thống" này. "SoundSystem" có thể xử lý các thành phần "Âm thanh". Mỗi hệ thống là một chức năng rộng hoạt động trên một hoặc nhiều loại dữ liệu.
Điều đó khiến chúng ta có nhiều trường hợp, đối với bất kỳ loại thành phần nhất định, chỉ có một hoặc hai hệ thống sẽ truy cập chúng. Hmm, điều đó có vẻ như sẽ giúp ích cho sự an toàn của luồng và hoàn toàn đưa sự tranh chấp về mức tối thiểu.
Hơn nữa, tôi cố gắng nghĩ về cách thực hiện đồng nhất dữ liệu. Thay vì thích:
for each thing:
play with it
cuddle it
kill it
Tôi tìm cách chia nó thành nhiều lượt, đơn giản hơn:
for each thing:
play with it
for each thing:
cuddle it
for each thing:
kill it
Điều đó đôi khi yêu cầu lưu trữ một số trạng thái trung gian cho quá trình trì hoãn đồng nhất tiếp theo để xử lý nhưng tôi thấy điều đó thực sự giúp tôi duy trì và suy luận về mã, biết rằng mỗi vòng lặp có logic đơn giản hơn, thống nhất hơn. Và này, có vẻ như nó sẽ đơn giản hóa sự an toàn của luồng và giảm sự tranh chấp của luồng.
Và bạn cứ tiếp tục như vậy cho đến khi bạn thấy rằng bạn có một kiến trúc thực sự dễ dàng song song với sự tự tin về tính an toàn và tính chính xác của luồng, nhưng tất cả ban đầu với trọng tâm là biểu diễn dữ liệu, có nhiều kiểu truy cập bộ nhớ dự đoán hơn, giảm sử dụng bộ nhớ, đơn giản hóa các luồng điều khiển để vượt qua đồng nhất hơn, giảm số lượng chức năng trong hệ thống của bạn gây ra tác dụng phụ mà không phải chịu chi phí sao chép sâu rất tốn kém, thống nhất API của bạn, v.v.
Khi bạn kết hợp tất cả những điều này, bạn có xu hướng kết thúc với một hệ thống giảm thiểu lượng trạng thái chia sẻ nơi bạn tình cờ tìm thấy một thiết kế thực sự thân thiện để đồng thời. Và nếu bất kỳ trạng thái nào cần được chia sẻ, bạn thường thấy nó không có nhiều tranh cãi khi sử dụng đồng bộ hóa mà không gây ra tắc nghẽn luồng, và hơn nữa, nó thường có thể được xử lý bởi cấu trúc dữ liệu trung tâm của bạn để thống nhất biểu diễn tất cả mọi thứ trong hệ thống để bạn không phải áp dụng đồng bộ hóa luồng cho hàng trăm địa điểm khác nhau, chỉ một số ít.
Bây giờ khi chúng tôi đi sâu vào một trong những thành phần phức tạp hơn như mắt lưới, chúng tôi lặp lại quy trình thiết kế tương tự, bắt đầu bằng việc nghĩ về dữ liệu trước tiên. Và nếu chúng ta làm đúng, chúng ta thậm chí có thể dễ dàng xử lý song song việc xử lý một lưới, nhưng thiết kế kiến trúc rộng hơn mà chúng ta thiết lập đã cho phép chúng ta xử lý song song nhiều lưới.