Wiederverwendbare Multithread-Implementierung in Sprite Kit

8

Ich arbeite an einem Sprite Kit-Spiel und ich muss etwas Multithreading machen, um die gesunden fps zu erhalten.

Beim Update rufe ich eine Funktion auf, um viele UIBezierPaths zu erstellen und sie mit einer statischen C ++ Bibliothek zusammenzuführen.

Wenn ich mehr als 10 Formen habe, fällt die Bildrate dramatisch ab. Deshalb habe ich beschlossen, GCD auszuprobieren und zu versuchen, das Problem mit einem separaten Thread zu lösen.

Ich habe dies in didMoveToView:

%Vor%

und in der Funktion, die für jeden Rahmen aufgerufen wird, rufe ich folgendes auf:

%Vor%

Für jemanden, der GCD gut kennt, könnte es offensichtlich sein, dass es bei jedem Frame einen neuen Thread erstellt, aber für mich war es noch nicht klar.

Meine Frage ist, gibt es eine Möglichkeit, einen Thread, den ich auf Update aufrufen möchte, wieder zu verwenden?

Vielen Dank für Ihre Hilfe im Voraus!

    
Velykovits 26.02.2015, 17:21
quelle

2 Antworten

18

Wenn Sie für jeden Frame Arbeit haben, die erledigt werden muss, bevor der Frame gerendert wird, wird Multithreading wahrscheinlich nicht helfen, es sei denn, Sie sind bereit, viel Mühe darauf zu verwenden.

Bei der Bildrate geht es nur um Zeit - nicht um CPU-Ressourcen, sondern nur um die Wandzeit. Um eine Framerate von 60fps zu behalten, haben Sie 16,67 ms, um all Ihre Arbeit zu erledigen. (Eigentlich weniger, weil SpriteKit und OpenGL etwas von dieser Zeit benötigen, um die Ergebnisse Ihrer Arbeit zu rendern.) Dies ist ein synchrones Problem - Sie haben Arbeit, Sie haben eine bestimmte Menge an Zeit, um es zu tun, so der erste Schritt zur Verbesserung der Leistung ist es, weniger Arbeit zu tun oder es effizienter zu tun.

Multithreading hingegen ist im Allgemeinen für asynchrone Probleme - es gibt Arbeit, die Sie tun müssen, aber es muss nicht gerade fertig gemacht werden, damit Sie mit dem anderen weitermachen können Dinge, die Sie gerade tun müssen (wie zum Beispiel die Rückkehr von Ihrer Update-Methode innerhalb von 16 ms, um Ihre Framerate aufrecht zu erhalten) und später die Ergebnisse dieser Arbeit überprüfen (zB in einem späteren Frame).

Es gibt jedoch ein wenig Spielraum zwischen diesen beiden Definitionen: Fast alle modernen iOS-Geräte verfügen über Multicore-CPUs. Wenn Sie also Ihre Karten richtig ausspielen, können Sie ein kleines bisschen Asynchronität in Ihr synchrones Problem einbauen > parallelisieren Ihre Arbeitslast. Dies zu tun und es gut zu machen, ist keine kleine Leistung - es war das Thema der ernsthaften Forschung und Investitionen von große Spielestudios für Jahre.

Sehen Sie sich die Abbildung unter " Wie eine Szene Frames von Animationen verarbeitet " im SpriteKit Programmierhandbuch. Das ist deine 16-ms-Uhr. Die hellblauen Bereiche sind Scheiben dieser 16 ms, für die Apples Code SpriteKit (und OpenGL und andere Systemframeworks) zuständig ist. Die anderen Scheiben gehören dir. Lassen Sie uns das Diagramm für ein besseres Aussehen aufrollen:

Wenn Sie zu viel in einem dieser Slices arbeiten oder die Arbeitslast von SpriteKit zu groß wird, wird das Ganze größer als 16 ms und Ihre Framerate sinkt.

Die Möglichkeit zum Threading besteht darin, während dieser Zeitleiste etwas Arbeit an der anderen CPU zu erledigen. Wenn SpriteKits Umgang mit Aktionen, Physik und Einschränkungen nicht von dieser Arbeit abhängt, können Sie dies parallel zu diesen Dingen tun:

Oder, wenn Ihre Arbeit stattfinden muss, bevor SpriteKit Aktionen ausführt & amp; Physik, aber Sie haben andere Arbeit, die Sie in der update -Methode tun müssen, können Sie einen Teil der Arbeit an einen anderen Thread senden, während Sie den Rest Ihrer update -Arbeit machen, und dann in Ihrer% co_de nach Ergebnissen suchen % Methode:

Wie kann man diese Dinge erreichen? Hier ist ein Ansatz mit Dispatch-Gruppen und der Annahme, dass Aktionen / Physik / Constraints nicht von Ihrer Hintergrundarbeit abhängen - es ist völlig übertrieben, also ist es vielleicht nicht das Beste. :)

%Vor%

Natürlich wird update , wie der Name schon sagt, die Ausführung blockieren, um zu warten, bis Ihre Arbeit im Hintergrund abgeschlossen ist. Sie haben also immer noch diese 16-ms-Zeitbeschränkung. Wenn die Vordergrundarbeit (der Rest von dispatch_group_wait , sowie die Aktionen / Physik / Einschränkungen von SpriteKit funktionieren und alle anderen Arbeiten, die als Antwort auf diese Dinge ausgeführt werden) vor der Hintergrundarbeit erledigt werden, warten Sie darauf es. Und wenn die Hintergrundarbeit und das Rendern von SpriteKit (plus was auch immer Sie in update machen, bevor Sie den Hintergrund erstellen) länger als 16 ms dauert, werden Sie immer noch Frames löschen. Der Trick dabei ist, Ihre Arbeitslast ausreichend detailliert zu kennen, um sie gut einzuplanen.

    
rickster 26.02.2015, 20:05
quelle
2

Betrachten Sie einen etwas anderen Ansatz. Erstellen und pflegen Sie Ihre eigene Warteschlange, anstatt eine Systemwarteschlange zu erhalten.

a) Rufen Sie dispatch_queue_create auf, um eine neue Warteschlange zu erstellen, und speichern Sie diese Warteschlange in Ihrem Objekt. Verwenden Sie dispatch_async in dieser Warteschlange, um Ihren Job auszuführen. Möglicherweise müssen Sie synchronisieren, wenn dies vor dem nächsten Frame usw. abgeschlossen werden muss.

b) Wenn Sie mehrere Jobs haben, sollten Sie eine Warteschlange mit gleichzeitigen Rechten anstelle einer seriellen Warteschlange in Erwägung ziehen, die je nach Ihren Abhängigkeiten möglicherweise "schneller" ist.

Mit GCD sollten Sie nicht über Threads nachdenken, wenn neue Threads erstellt / wiederverwendet werden usw. Denken Sie nur an die Warteschlangen und daran, was Sie an sie weitergeben. Das Lesen von Apples Concurrency Programming Guide und Referenz auf gcd wird hoffentlich auch helfen.

    
KirkSpaziani 26.02.2015 17:42
quelle