Đồng bộ hóa giữa luồng logic trò chơi và luồng kết xuất


16

Làm thế nào để một logic trò chơi riêng biệt và kết xuất? Tôi biết dường như đã có câu hỏi ở đây hỏi chính xác điều đó nhưng câu trả lời không thỏa đáng với tôi.

Từ những gì tôi hiểu cho đến nay, việc phân tách chúng thành các luồng khác nhau để logic trò chơi có thể bắt đầu chạy cho tích tắc tiếp theo ngay lập tức thay vì chờ vsync tiếp theo nơi kết xuất cuối cùng trả về từ cuộc gọi trao đổi được chặn.

Nhưng cụ thể cấu trúc dữ liệu nào được sử dụng để ngăn chặn các điều kiện chạy đua giữa luồng logic trò chơi và luồng kết xuất. Có lẽ luồng kết xuất cần truy cập vào các biến khác nhau để tìm ra những gì cần vẽ, nhưng logic trò chơi có thể đang cập nhật cùng các biến này.

Có một kỹ thuật tiêu chuẩn thực tế để xử lý vấn đề này. Có thể giống như sao chép dữ liệu cần thiết của luồng kết xuất sau mỗi lần thực hiện logic trò chơi. Dù giải pháp là gì thì chi phí đồng bộ hóa hay bất cứ điều gì sẽ ít hơn là chỉ chạy mọi thứ đơn lẻ?


1
Tôi ghét chỉ spam một liên kết, nhưng tôi nghĩ rằng đây là một bài đọc rất hay và nó sẽ trả lời tất cả các câu hỏi của bạn: altdevblogaday.com/2011/07/03/threading-and-your-game-loop
Roy T.


1
Những liên kết này cho kết quả cuối cùng điển hình mà người ta muốn, nhưng không nói chi tiết về cách thực hiện. Bạn sẽ sao chép toàn bộ biểu đồ cảnh từng khung hình hay cái gì khác? Các cuộc thảo luận quá cao và mơ hồ.
dùng782220

Tôi nghĩ rằng các liên kết khá rõ ràng về bao nhiêu trạng thái được sao chép trong mỗi trường hợp. ví dụ. (từ liên kết thứ 1) "Một lô chứa tất cả thông tin cần thiết để vẽ khung, nhưng không chứa bất kỳ trạng thái trò chơi nào khác." hoặc (từ liên kết thứ 2) "Tuy nhiên, dữ liệu vẫn cần được chia sẻ, nhưng bây giờ thay vì mỗi hệ thống truy cập vào một vị trí dữ liệu chung để nói, lấy dữ liệu vị trí hoặc định hướng, mỗi hệ thống có một bản sao riêng" (Xem đặc biệt là 3.2.2 - Trạng thái Quản lý)
DMGregory

Bất cứ ai đã viết rằng bài báo của Intel dường như không biết rằng phân luồng cấp cao nhất là một ý tưởng rất tồi. Không ai làm điều gì đó ngu ngốc. Đột nhiên, toàn bộ ứng dụng phải giao tiếp thông qua các kênh chuyên biệt và có các khóa và / hoặc trao đổi nhà nước phối hợp lớn ở khắp mọi nơi. Chưa kể rằng không biết khi nào dữ liệu được gửi sẽ được xử lý nên rất khó để suy luận về những gì mã làm. Việc sao chép dữ liệu cảnh có liên quan sẽ dễ dàng hơn nhiều (không thay đổi được như con trỏ giới thiệu, có thể thay đổi - theo giá trị) tại một điểm và để hệ thống con sắp xếp nó theo cách nó muốn.
rắn5

Câu trả lời:


1

Tôi đã làm việc trên cùng một điều. Mối quan tâm bổ sung là OpenGL (và theo hiểu biết của tôi, OpenAL) và một số giao diện phần cứng khác, là các máy trạng thái hiệu quả không phù hợp với việc được gọi bởi nhiều luồng. Tôi không nghĩ rằng hành vi của họ thậm chí được xác định và đối với LWJGL (cũng có thể là JOGL), nó thường ném ra một ngoại lệ.

Điều cuối cùng tôi làm là tạo ra một chuỗi các luồng thực hiện một giao diện cụ thể và tải chúng lên ngăn xếp của đối tượng điều khiển. Khi đối tượng đó nhận được tín hiệu để tắt trò chơi, nó sẽ chạy qua từng luồng, gọi một phương thức ceaseOperations () đã triển khai và đợi chúng đóng lại trước khi tự đóng. Dữ liệu phổ quát có thể liên quan đến kết xuất âm thanh, đồ họa hoặc bất kỳ dữ liệu nào khác được lưu giữ trong một chuỗi các đối tượng dễ bay hơi hoặc có sẵn cho tất cả các luồng nhưng không bao giờ được lưu trong bộ nhớ luồng. Có một hình phạt hiệu suất nhỏ ở đó, nhưng được sử dụng đúng cách, nó cho phép tôi linh hoạt gán âm thanh cho một luồng, đồ họa cho một luồng khác, vật lý cho một luồng khác, v.v. mà không cần buộc chúng vào "vòng lặp trò chơi" truyền thống (và đáng sợ).

Vì vậy, theo quy tắc, tất cả các cuộc gọi OpenGL đều đi qua luồng Đồ họa, tất cả OpenAL thông qua luồng Âm thanh, tất cả đầu vào qua luồng Đầu vào và tất cả những gì mà luồng điều khiển tổ chức cần phải lo lắng là quản lý luồng. Trạng thái trò chơi được tổ chức trong lớp GameState, tất cả họ có thể xem khi họ cần. Nếu tôi quyết định điều đó, giả sử, JOAL đã hẹn hò và tôi muốn sử dụng phiên bản mới của JavaSound, tôi chỉ thực hiện một luồng khác cho Âm thanh.

Hy vọng bạn thấy những gì tôi đang nói, tôi đã có vài ngàn dòng về dự án này rồi. Nếu bạn muốn tôi thử và cùng nhau lấy một mẫu, tôi sẽ xem tôi có thể làm gì.


Vấn đề cuối cùng bạn sẽ gặp phải là thiết lập này không có quy mô đặc biệt tốt trên máy đa lõi. Vâng, có những khía cạnh của một trò chơi thường được phục vụ tốt nhất trong luồng của riêng họ, chẳng hạn như âm thanh, nhưng phần lớn phần còn lại của vòng lặp trò chơi thực sự có thể được quản lý một cách thanh thản kết hợp với các tác vụ nhóm luồng. Nếu nhóm luồng của bạn hỗ trợ mặt nạ ái lực, bạn có thể dễ dàng xếp hàng các tác vụ kết xuất được thực hiện trên cùng một luồng và để bộ lập lịch xử lý của bạn quản lý hàng đợi xử lý luồng và thực hiện công việc đánh cắp khi cần để hỗ trợ đa luồng và đa lõi.
Naros

1

Thông thường, logic liên quan đến kết xuất đồ họa (và lịch trình của chúng và khi chúng chạy, v.v.) được xử lý bởi một luồng riêng biệt. Tuy nhiên, luồng đó đã được triển khai (lên và chạy) bởi nền tảng bạn sử dụng để phát triển vòng lặp trò chơi (và trò chơi) của mình.

Vì vậy, để có được vòng lặp trò chơi trong đó logic trò chơi cập nhật độc lập với lịch làm mới đồ họa, bạn không cần tạo thêm luồng, bạn chỉ cần nhấn vào luồng đã có sẵn để cập nhật đồ họa.

Điều này phụ thuộc vào nền tảng bạn đang sử dụng. Ví dụ:

  • nếu bạn đang làm điều đó trong hầu hết các nền tảng liên quan đến Open GL ( GLUT cho C / C ++ , JOLG cho Java , Hành động liên quan đến OpenGL ES của Android ), họ sẽ thường cung cấp cho bạn một phương thức / chức năng được gọi theo định kỳ bởi luồng kết xuất và bạn có thể tích hợp vào vòng lặp trò chơi của bạn (mà không làm cho các lần lặp của gameloop phụ thuộc vào thời điểm phương thức đó được gọi). Đối với GLUT bằng C, bạn làm một cái gì đó như thế này:

    glutDisplayFunc (myFunctionForGraphicsDrawing);

    glutIdleFunc (myFunctionForUpdatingState);

  • trong JavaScript, bạn có thể sử dụng Công nhân web vì không có đa luồng (bạn có thể tiếp cận chương trình) , bạn cũng có thể sử dụng cơ chế "requestAnimationFrame" để được thông báo khi kết xuất đồ họa mới sẽ được lên lịch và cập nhật trạng thái trò chơi của bạn .

Về cơ bản những gì bạn muốn là một vòng lặp trò chơi hỗn hợp: bạn có một số mã cập nhật trạng thái trò chơi và được gọi bên trong chuỗi chính của trò chơi của bạn và bạn cũng muốn định kỳ truy cập vào (hoặc được gọi lại bởi) chủ đề kết xuất đồ họa hiện có cho đến khi đến lúc phải làm mới đồ họa.


0

Trong Java có từ khóa "được đồng bộ hóa", khóa các biến bạn truyền cho nó để làm cho chúng an toàn. Trong C ++, bạn có thể đạt được điều tương tự bằng Mutex. Ví dụ:

Java:

synchronized(a){
    //code using a
}

C ++:

mutex a_mutex;

void f(){
    a_mutex.lock();
    //code using a
    a_mutex.unlock();
}

Khóa các biến đảm bảo chúng không thay đổi trong khi chạy mã theo mã, vì vậy các biến không bị thay đổi bởi luồng cập nhật của bạn trong khi bạn hiển thị chúng (thực tế là chúng thay đổi, nhưng từ quan điểm của luồng kết xuất của bạn, chúng không ' t). Mặc dù vậy, bạn phải coi chừng với từ khóa được đồng bộ hóa trong Java, vì nó chỉ đảm bảo con trỏ tới biến / Object không thay đổi. Các thuộc tính vẫn có thể thay đổi mà không thay đổi con trỏ. Để suy ngẫm về điều này, bạn có thể tự sao chép đối tượng hoặc gọi đồng bộ hóa trên tất cả các thuộc tính của đối tượng bạn không muốn thay đổi.


1
Mutexes không nhất thiết phải là câu trả lời ở đây vì OP không chỉ cần tách rời logic và kết xuất trò chơi mà họ còn muốn tránh mọi khả năng của một luồng bị đình trệ trong quá trình xử lý bất kể luồng nào đang xử lý ở đâu. vòng.
Naros

0

Những gì tôi thường thấy để xử lý giao tiếp logic / kết xuất luồng là tăng gấp ba bộ đệm dữ liệu của bạn. Bằng cách này, chủ đề kết xuất đã nói xô 0 nó đọc từ. Chuỗi logic sử dụng nhóm 1 làm nguồn đầu vào cho khung tiếp theo và ghi dữ liệu khung vào nhóm 2.

Tại các điểm đồng bộ hóa, các chỉ số về ý nghĩa của từng nhóm trong ba nhóm được hoán đổi để dữ liệu của khung tiếp theo được cung cấp cho luồng kết xuất và luồng logic có thể tiếp tục chuyển tiếp.

Nhưng không nhất thiết phải có một lý do để phân chia kết xuất & logic thành các luồng tương ứng của chúng. Trong thực tế, bạn có thể giữ nối tiếp vòng lặp trò chơi và tách tốc độ khung hình kết xuất của bạn khỏi bước logic bằng cách sử dụng phép nội suy. Để tận dụng các bộ xử lý đa lõi sử dụng loại thiết lập này là nơi bạn sẽ có một nhóm luồng hoạt động trên các nhóm tác vụ. Các tác vụ này có thể chỉ đơn giản là những thứ như thay vì lặp lại danh sách các đối tượng từ 0 đến 100, bạn lặp lại danh sách trong 5 nhóm 20 trên 5 luồng giúp tăng hiệu suất của bạn nhưng không làm phức tạp vòng lặp chính.


0

Đây là một bài viết cũ nhưng nó vẫn bật lên vì vậy muốn thêm 2 xu của tôi vào đây.

Danh sách đầu tiên dữ liệu nên được lưu trữ trong UI / luồng hiển thị so với luồng logic. Trong luồng UI, bạn có thể bao gồm lưới 3d, kết cấu, thông tin ánh sáng và bản sao dữ liệu vị trí / xoay / hướng.

Trong luồng logic trò chơi, bạn có thể cần kích thước đối tượng trò chơi ở chế độ 3d, nguyên thủy giới hạn (hình cầu, khối lập phương), dữ liệu lưới 3d đơn giản hóa (ví dụ như va chạm chi tiết), tất cả các thuộc tính ảnh hưởng đến chuyển động / hành vi, như vận tốc của đối tượng, tỷ lệ xoay, v.v. và dữ liệu vị trí / xoay / hướng.

Nếu bạn so sánh hai danh sách, bạn có thể thấy rằng chỉ sao chép dữ liệu vị trí / xoay / hướng cần được truyền từ logic sang luồng UI. Bạn cũng có thể cần một số loại id tương quan để xác định đối tượng trò chơi mà dữ liệu này thuộc về.

Làm thế nào bạn làm điều đó phụ thuộc vào ngôn ngữ bạn đang làm việc với. Trong Scala, bạn có thể sử dụng Bộ nhớ giao dịch phần mềm, trong Java / C ++ một số loại khóa / đồng bộ hóa. Tôi thích dữ liệu bất biến vì vậy tôi có xu hướng trả về đối tượng bất biến mới cho mỗi bản cập nhật. Đây là một chút lãng phí bộ nhớ nhưng với máy tính hiện đại, nó không phải là một vấn đề lớn. Tuy nhiên, nếu bạn muốn khóa cấu trúc dữ liệu chia sẻ, bạn có thể làm điều đó. Kiểm tra lớp Exchanger trong Java, sử dụng hai hoặc nhiều bộ đệm có thể tăng tốc mọi thứ.

Trước khi bạn bắt đầu chia sẻ dữ liệu giữa các luồng, hãy tính xem bạn cần truyền bao nhiêu dữ liệu. Nếu bạn có một octree phân vùng không gian 3d của bạn và bạn có thể thấy 5 đối tượng trò chơi trong tổng số 10 đối tượng, ngay cả khi logic của bạn cần cập nhật tất cả 10 bạn chỉ cần vẽ lại 5 đối tượng bạn đang nhìn thấy. Để đọc thêm, hãy xem blog này: http://gameprogrammingpotypes.com/game-loop.html Đây không phải là về đồng bộ hóa nhưng nó cho thấy cách logic trò chơi được tách khỏi màn hình và những thách thức bạn cần vượt qua (FPS). Hi vọng điêu nay co ich,

dấu

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.