Oyun mantığı iş parçacığı ve oluşturma iş parçacığı arasında senkronizasyon


16

Bir oyun mantığı ve renderleme nasıl ayrılır? Burada tam olarak bunu soran sorular var gibi görünüyor ama cevaplar benim için tatmin edici değil.

Şimdiye kadar anladığım kadarıyla, onları farklı iş parçacıklarına ayırmanın amacı, oyun mantığının, son olarak swapbuffer çağrısının engelleme çağrısından döndüğü bir sonraki vsync'i beklemek yerine hemen bir sonraki kene için çalışmaya başlamasıdır.

Ancak özellikle oyun mantığı iş parçacığı ve oluşturma iş parçacığı arasındaki yarış koşullarını önlemek için hangi veri yapılarının kullanıldığı. Muhtemelen oluşturma iş parçacığının ne çizileceğini anlamak için çeşitli değişkenlere erişmesi gerekiyor, ancak oyun mantığı aynı değişkenleri güncelliyor olabilir.

Bu sorunu çözmek için fiili bir standart teknik var mı? Belki de oyun mantığının her yürütülmesinden sonra görüntü oluşturma iş parçacığının ihtiyaç duyduğu verileri kopyalamak gibi. Çözüm ne olursa olsun, senkronizasyon yükü veya her şeyi tek iş parçacıklı çalıştırmaktan daha az ne olacak?


1
Sadece bir bağlantı spam spam nefret ediyorum, ama bu çok iyi bir okuma olduğunu düşünüyorum ve tüm sorularınıza cevap gerekir: altdevblogaday.com/2011/07/03/threading-and-your-game-loop
Roy T.


1
Bu bağlantılar, kişinin istediği tipik sonuç sonucunu verir, ancak nasıl yapılacağı konusunda ayrıntılı bilgi vermez. Tüm sahne grafiğini her kareyi veya başka bir şeyi kopyalar mısınız? Tartışmalar çok yüksek ve belirsiz.
user782220

Her durumda ne kadar devletin kopyalandığına dair bağlantıların oldukça açık olduğunu düşündüm. Örneğin. (1. bağlantıdan) "Bir grup, bir kare çizmek için gerekli tüm bilgileri içerir, ancak başka bir oyun durumu içermez." veya (2. bağlantıdan) "Verilerin yine de paylaşılması gerekiyor, ancak şimdi konum veya yönlendirme verisi almak için ortak bir veri konumuna erişen her sistem yerine her sistemin kendi kopyası var" (Özellikle 3.2.2 - Durum Manager)
DMGregory

Intel makalesinin kim yazdığını, üst düzey diş açmanın çok kötü bir fikir olduğunu bilmiyor gibi görünüyor. Kimse bu kadar aptalca bir şey yapmaz. Aniden tüm uygulama özel kanallar üzerinden iletişim kurmak zorunda ve her yerde kilitler ve / veya devasa koordine edilmiş devlet alışverişleri var. Gönderilen verilerin ne zaman işleneceğini söyleyecek olmadıklarından bahsetmiyoruz, bu nedenle kodun ne yaptığı hakkında mantık yürütmek son derece zor. İlgili sahne verilerini (ref. Sayılan işaretçiler olarak değiştirilemez, değere göre değiştirilebilir) kopyalamak ve alt sistemin istediği gibi sıralamasına izin vermek çok daha kolaydır.
snake5

Yanıtlar:


1

Aynı şey üzerinde çalışıyorum. Ek endişe, OpenGL (ve bildiklerime göre, OpenAL) ve bir dizi diğer donanım arayüzünün, birden çok iş parçacığı tarafından çağrılma ile iyi geçinmeyen makineleri etkin bir şekilde devletler olmasıdır. Davranışlarının bile tanımlandığını sanmıyorum ve LWJGL (muhtemelen ayrıca JOGL) için genellikle bir istisna atar.

Yaptığım şey, belirli bir arabirimi uygulayan bir dizi iş parçacığı oluşturmak ve bunları bir denetim nesnesinin yığınına yüklemekti. Bu nesne oyunu kapatmak için bir sinyal aldığında, her bir iş parçacığından geçer, uygulanmış bir ceaseOperations () yöntemini çağırır ve kendisini kapatmadan önce kapanmalarını bekler. Ses, grafik veya diğer verilerin oluşturulmasıyla ilgili olabilecek evrensel veriler, geçici veya tüm iş parçacıkları için evrensel olarak kullanılabilir ancak hiçbir zaman iş parçacığı belleğinde tutulmayan bir nesne dizisinde tutulur. Orada hafif bir performans cezası var, ancak düzgün bir şekilde kullanıldığında, geleneksel bir (ve korkunç) oyun döngüsüne bağlamadan bir iş parçacığına, başka bir grafiklere, fiziğe, diğerine fizik vb. Esnek bir şekilde ses atamamı sağladı.

Kural olarak, tüm OpenGL çağrıları Grafik iş parçacığından, tüm OpenAL Ses iş parçacığından, Giriş iş parçacığı aracılığıyla tüm girdilerden ve düzenleme denetim iş parçacığının endişelenmesi gereken tek şey iş parçacığı yönetimidir. Oyun durumu GameState sınıfında tutulur ve hepsi ihtiyaç duydukları yere bakabilirler. Diyelim ki, JOAL tarihli ve onun yerine JavaSound yeni sürümünü kullanmak istiyorum karar verirseniz, ben sadece Ses için farklı bir iş parçacığı uygulamak.

Umarım ne dediğimi görürsünüz, bu projede zaten birkaç bin satır var. Bir örneği bir araya getirip kazımamı isterseniz, ne yapabileceğimi göreceğim.


Sonunda karşılaşacağınız sorun, bu kurulumun çok çekirdekli bir makinede özellikle iyi ölçeklenmemesidir. Evet, bir oyunun genellikle ses gibi kendi iş parçacığında en iyi şekilde sunulan yönleri vardır, ancak oyun döngüsünün geri kalanının çoğu aslında iş parçacığı havuzu görevleri ile birlikte seri olarak yönetilebilir. İş parçacığı havuzunuz benzeşim maskelerini destekliyorsa, aynı iş parçacığında yürütülecek olan oluşturma görevlerini kolayca sıralayabilir ve iş parçacığı zamanlayıcınızın iş parçacığı kuyruklarını yönetmesini ve size çok iş parçacığı ve çok çekirdekli destek vererek gerektiğinde iş çalmasını sağlayabilirsiniz.
Naros

1

Genellikle, grafik oluşturma geçişleriyle (ve bunların zamanlamaları ve ne zaman çalıştırılacakları vb.) İlgili mantık ayrı bir iş parçacığı tarafından işlenir. Ancak bu iş parçacığı, oyun döngünüzü (ve oyununuzu) geliştirmek için kullandığınız platform tarafından zaten uygulanmış (çalışıyor ve çalışıyor).

Bu nedenle, oyun mantığının grafik yenileme zamanlamasından bağımsız olarak güncellendiği bir oyun döngüsü elde etmek için ekstra iş parçacığı yapmanız gerekmez, sadece söz konusu grafik güncellemeleri için zaten var olan iş parçacığına dokunursunuz.

Bu, hangi platformu kullandığınıza bağlıdır. Örneğin:

  • çoğu Open GL ile ilgili platformda ( C / C ++ için GLUT , Java için JOLG , Android'in OpenGL ES ile ilgili Eylem ) yapıyorsanız, genellikle işleme dizisi tarafından periyodik olarak çağrılan bir yöntem / işlev verir ve oyun döngünüze entegre edilebilir (gameloop'un yinelemelerini bu yöntemin ne zaman çağrıldığına bağlı olmadan). C kullanarak GLUT için şu şekilde bir şey yaparsınız:

    glutDisplayFunc (myFunctionForGraphicsDrawing);

    glutIdleFunc (myFunctionForUpdatingState);

  • JavaScript'te Web İşçileri'ni kullanabilirsiniz, çünkü çoklu iş parçacığı yoktur (programlı olarak erişebilirsiniz) , ayrıca yeni bir grafik oluşturma zamanlandığında bildirim almak ve oyun durumunuzu buna göre güncellemek için "requestAnimationFrame" mekanizmasını kullanabilirsiniz. .

Temel olarak istediğiniz şey karışık bir adım oyun döngüsüdür: oyun durumunu güncelleyen ve oyununuzun ana iş parçacığının içinde çağrılan bazı kodlarınız var ve ayrıca periyodik olarak zaten Mevcut grafik oluşturma iş parçacığı, grafiklerin ne zaman yenileneceği konusunda kafalar için.


0

Java'da geçirdiğiniz değişkenleri güvenli hale getirmek için kilitlediğiniz "senkronize" anahtar kelimesi vardır. C ++ 'da aynı şeyi Mutex kullanarak elde edebilirsiniz. Örneğin:

Java:

synchronized(a){
    //code using a
}

C ++:

mutex a_mutex;

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

Değişkenleri kilitlemek, kodu izleyen kodu çalıştırırken değişmemelerini sağlar, bu nedenle değişkenleri siz oluştururken güncelleme iş parçanız tarafından değişmez (aslında değişirler DO, ancak oluşturma iş parçanızın bakış açısından t). Java'da senkronize edilmiş anahtar kelimeyle dikkat etmeniz gerekir, çünkü yalnızca / Object değişkeninin göstergesinin değişmediğinden emin olur. Nitelikler işaretçiyi değiştirmeden değişebilir. Bunu düşünmek için, nesneyi kendiniz kopyalayabilir veya değiştirmek istemediğiniz nesnenin tüm nitelikleri üzerinde senkronize edilmiş olarak çağırabilirsiniz.


1
Muteksler mutlaka burada cevap değildir, çünkü OP sadece oyun mantığını ve renderlemeyi ayırmak zorunda kalmayacak, aynı zamanda diğer bir iş parçacığının şu anda işlemde olabileceği yerden bağımsız olarak bir iş parçacığının işleyişinde ilerleyebilmesini engellemekten kaçınmak istemektedir. döngü.
Naros

0

Genelde mantık / iş parçacığı iletişim işlemek için gördüklerimi verilerinizi üçe tamponlamaktır. Bu şekilde, render iş parçacığı okuduğu kepçe 0'ı söyler. Mantıksal iş parçacığı, sonraki kare için giriş kaynağı olarak bölüm 1'i kullanır ve kare verilerini bölüm 2'ye yazar.

Senkronizasyon noktalarında, üç bölümün her birinin ne anlama geldiğine ilişkin endeksler değiştirilir, böylece bir sonraki karenin verileri oluşturma iş parçacığına verilir ve mantık iş parçacığı ileri devam edebilir.

Ancak, oluşturma ve mantığı ilgili iş parçacıklarına ayırmak için bir neden yoktur. Aslında oyun döngüsünü seri halde tutabilir ve enterpolasyon kullanarak render kare hızınızı mantık adımından ayırın. Bu tür bir kurulumu kullanarak çok çekirdekli işlemcilerden yararlanmak için, görev grupları üzerinde çalışan bir iş parçacığı havuzunuz olacaktır. Bu görevler, 0'dan 100'e kadar bir nesne listesini yinelemek yerine, performansınızı etkili bir şekilde artıran ancak ana döngüyü fazla karmaşıklaştırmayan 5 parçanın 20'sinden oluşan 5 kova halinde listeyi tekrarlarsınız.


0

Bu eski bir yazı ama yine de açılır, bu yüzden buraya 2 sent eklemek istedim.

İlk olarak UI / display iş parçacığında veya mantık iş parçacığında saklanması gereken verileri listeleyin. UI iş parçacığına 3B kafes, dokular, ışık bilgileri ve konum / dönüş / yön verilerinin bir kopyasını ekleyebilirsiniz.

Oyun mantığı iş parçacığında, oyun nesnesi boyutunun 3 boyutlu olması, sınırlayıcı ilkeler (küre, küp), basitleştirilmiş 3B kafes verileri (örneğin ayrıntılı çarpışmalar için), nesne hızını, dönüş oranını vb. ve ayrıca konum / dönüş / yön verisi.

İki listeyi karşılaştırırsanız, yalnızca konum / dönüş / yön verilerinin kopyasının mantıktan UI iş parçacığına geçirilmesi gerektiğini görebilirsiniz. Bu verilerin hangi oyun nesnesine ait olduğunu belirlemek için bir tür korelasyon kimliğine de ihtiyacınız olabilir.

Bunu nasıl yapacağınız, hangi dilde çalıştığınıza bağlıdır. Scala'da Java / C ++ yazılımında bir çeşit kilitleme / senkronizasyon için Yazılım İşlem Belleğini kullanabilirsiniz. Değişmez verileri seviyorum, bu yüzden her güncelleme için yeni değişmez nesne döndürme eğilimindeyim. Bu biraz bellek kaybıdır, ancak modern bilgisayarlarda bu kadar önemli değil. Yine de paylaşılan veri yapılarını kilitlemek istiyorsanız bunu yapabilirsiniz. Java'da Exchanger sınıfına göz atın, iki veya daha fazla arabellek kullanarak işleri hızlandırabilirsiniz.

İş parçacıkları arasında veri paylaşmaya başlamadan önce, gerçekte ne kadar veri aktarmanız gerektiğini hesaplayın. 3B alanınızı bölümleyen bir oktree varsa ve toplam 10 nesneden 5 oyun nesnesini görebilirsiniz, mantığınızın 10'u güncellemesi gerekse bile yalnızca gördüğünüz 5'i yeniden çizmeniz gerekir. Daha fazla okumak için bu bloga göz atın : http://gameprogrammingpatterns.com/game-loop.html Bu senkronizasyon ile ilgili değildir, ancak oyun mantığının ekrandan nasıl ayrıldığını ve üstesinden gelmeniz gereken zorlukları (FPS) gösterir. Bu yardımcı olur umarım,

işaret

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.