Spielschleife: Systemauslastung minimieren
-
O.k., wenn ich eine Spielschleife hab, so wie diese hier:
do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message != WM_QUIT) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { // Spielrelevantes Zeug } } while (msg.message != WM_QUIT);
dann springt die CPU-Auslastung aufgrund dieser Schleife automatisch auf 100 %. Wie kann ich das verhindern? Ich hab ja nichts dagegen, wenn das Spiel die Leistung wirklich braucht. Aber in dem Fall werden ja pauschal immer 100 % verbraucht, auch wenn das Programm an sich gar nichts macht, sondern nur durch die Schleife läuft.
-
Sleep(1)
oder so etwas in die Schleife tun (zum Beispiel nach dem spielrelevanten Zeug).
-
Ist das wirklich die übliche Vorgehensweise? Das wirkt auf mich eher wie so eine typische Hackernotlösung.
-
Du kannst auch alternativ die Framerate festlegen, 60 zum Beispiel, und vor jedem Durchlauf solange schlafen bis die 60stel Sekunde rum ist. Alternativ geht das mit Sync (Video Synchronisation), dabei wird das neu Zeichnen mit der Monitor-Neuzeichnung synchronisiert, weil alles was zwischen den Monitorupdates geändert wird eh nicht zu sehen ist.
Alternativ bastle dir einen Thread der alle 60stel Sekunden eine Updatemessage sendet. Das spielrelevante Zeug kommt dann in den Message Handler für die Update-Nachricht und zeichnet nur genau einen Frame und ist dann fertig. Dann benutzt du natürlich GetMessage, was dann blockiert wenn es nichts zu tun gibt.
Sleep(1) geht um erstmal die CPU nicht auf 100% laufen zu lassen, aber langfristig willst du den Sleep irgendwie an die Framerate koppeln.
-
NES-Spieler schrieb:
wenn das Spiel die Leistung wirklich braucht
Definiere "wenn das Spiel die Leistung wirklich braucht". Wenn mein Spiel z.b. ein kleines rotes Quadrat so oft zeichnen soll, wie es der PC hergibt - dann braucht es per Definition 100% Prozessorleistung. f'`8k
AutocogitoGruß, TGGC (Was Gamestar sagt...)
-
TGGC schrieb:
Wenn mein Spiel z.b. ein kleines rotes Quadrat so oft zeichnen soll, wie es der PC hergibt - dann braucht es per Definition 100% Prozessorleistung.
Naja, wenn es das kleine rote Quadrat nur einmal zeichnet sieht es genauso aus, also braucht es die restlichen Zeichnungen nicht.
Brauchen ist intuitiv so definiert, dass man es nicht weglassen kann, ohne die Funktionalität zu beeinträchtigen.
-
Aber das kleine rote Quadrat hat bei mir 1875 fps!!! So einen hohen Wert werde ich doch nicht durch Sleep kaputtmachen.
-
nwp2 schrieb:
TGGC schrieb:
Wenn mein Spiel z.b. ein kleines rotes Quadrat so oft zeichnen soll, wie es der PC hergibt - dann braucht es per Definition 100% Prozessorleistung.
Naja, wenn es das kleine rote Quadrat nur einmal zeichnet sieht es genauso aus, also braucht es die restlichen Zeichnungen nicht.
Brauchen ist intuitiv so definiert, dass man es nicht weglassen kann, ohne die Funktionalität zu beeinträchtigen.Du hast aber eben meine Definition schon abgeaendert, also wars das schon mit "intuituv" definiert. Vielleicht sagt der Gamedesigner irgendwann, das Quadrat huepft oder es blinkt kontinuierlich - was wird dann aus den restlichen Zeichnungen? BTW: ich kenne sehr wenige Spiele, die irgendwann einmal nichts Bewegtes auf den Bildschirm haben. f'`8k
AutocogitoGruß, TGGC (Was Gamestar sagt...)
-
Schalte den vsync ein.
-
NES-Spieler schrieb:
bool dirty=true; do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message != WM_QUIT) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { if(IstLogikNoetig()) { dirty=true; Logik(); } else if(dirty) { Render(); dirty=false; } else Sleep(1); } } while (msg.message != WM_QUIT);
-
TGGC schrieb:
Wenn mein Spiel z.b. ein kleines rotes Quadrat so oft zeichnen soll, wie es der PC hergibt - dann braucht es per Definition 100% Prozessorleistung.
Sowas kenne ich noch aus DOS-Zeiten. Spiele die davon ausgingen, daß alle PCs mit ~5Mhz laufen.
Btw, wenn sich das Quadrat nicht bewegt oder seine Größe ändert, mußt Du es nur einmal zeichnen.
-
Super Tipp. Vielen Dank!
-
So, jetzt komme ich endlich mal zum Antworten:
nwp2 schrieb:
Du kannst auch alternativ die Framerate festlegen, 60 zum Beispiel, und vor jedem Durchlauf solange schlafen bis die 60stel Sekunde rum ist.
Ja, aber wie macht man das? Dazu müßte ich doch auch wieder ständig per clock() abfragen, ob es schon soweit ist. Dadurch würde diese 100%-Auslastungsschleife ja nicht verschwinden, es sei denn, Du denkst an eine Möglichkeit, die mir noch nicht eingefallen ist. Oder wir wären wieder beim Sleep, was meiner Meinung nach immer noch eine Hackerlösung ist, da das Teil das gesamte Programm, inklusive Nachrichtenschleife, lahmlegt. Gut, ist zwar nicht wirklich zu bemerken bei ein paar Millisekunden, aber das wirkt auf mich trotzdem unsauber.
nwp2 schrieb:
Alternativ geht das mit Sync (Video Synchronisation), dabei wird das neu Zeichnen mit der Monitor-Neuzeichnung synchronisiert, weil alles was zwischen den Monitorupdates geändert wird eh nicht zu sehen ist.
Ist das einfach zu regeln oder ist das etwas Kompliziertes? Und wird dadurch mein Problem gelöst? Denn mein Problem ist ja nicht, daß er fürs Zeichnen 100 % Auslastung braucht, sondern daß er pauschal 100 % Auslastung hat, weil er in einer while-Schleife festhängt:
do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message != WM_QUIT) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { // Spielrelevantes Zeug } } while (msg.message != WM_QUIT);
Das allein macht schon 100 % der Leistung aus, auch wenn spieletechnisch noch gar nichts da drin steht. Wenn ich also schreiben würde:
clock_t lastUpdate = 0; do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message != WM_QUIT) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { if ((clock() - lastUpdate) * 1000 / CLOCKS_PER_SEC > duration) { // Spielrelevantes Zeug lastUpdate = clock(); } } } while (msg.message != WM_QUIT);
dann hätte ich immer noch 100 % Auslastung. Ich brauche eine Möglichkeit, die Wartezeit im spielrelevanten Teil prozeßschonend ablaufen zu lassen, ohne daß das Windows-Message-Zeug ebenfalls gestoppt wird. Also sowas ähnliches wie Sleep, nur daß es sich bloß auf die Stelle beim spielrelevanten Zeug bezieht.
nwp2 schrieb:
Alternativ bastle dir einen Thread der alle 60stel Sekunden eine Updatemessage sendet.
void GameThread() { while (gameIsRunning) { if ((clock() - lastUpdate) * 1000 / CLOCKS_PER_SEC > duration) { UpdateScreen(); lastUpdate = clock(); } } }
Dasselbe Problem: Allein die bloße Schleife bringt ja den Prozessor auf 100 % und nicht die Zeichenfunktion.
TGGC schrieb:
NES-Spieler schrieb:
wenn das Spiel die Leistung wirklich braucht
Definiere "wenn das Spiel die Leistung wirklich braucht".
Damit meine ich: Wenn das Spiel wirklich was macht. Beispiel:
while (true) { if (CheckIfItsTime()) DoSomething(); }
Wenn die Auslastung durch das DoSomething kommt, hab ich damit keine Probleme. Wenn die Auslastung aber dadurch kommt, daß er in einer while-Schleife bis zum Exzeß die Funktion CheckIfItsTime() aufruft, dann soll er keine große Auslastung haben, da das ja nur der Wartevorgang ist.
Noch etwas zu Sleep: Ist es nicht so, daß Sleep nur einen gewissen Minimalwert haben kann? Also Sleep(1) ist nicht wirklich nur eine Millisekunde. Wenn ich also daran denke, mit Sleep zu arbeiten, könnte ich auch gleich einen WM_TIMER mit der Dauer von 1 nehmen, um meine Spiellogik anzusprechen. Das wäre wenigstens programmiertechnisch sauber. Aber einen WM_TIMER nimmt man nicht, weil er eben zu langsam ist. Und meines Wissens nach ist die minimale Dauer eines Timers identisch mit der minimalen Dauer von Sleep oder irre ich mich da?
-
NES-Spieler schrieb:
Ich brauche eine Möglichkeit, die Wartezeit im spielrelevanten Teil prozeßschonend ablaufen zu lassen, ohne daß das Windows-Message-Zeug ebenfalls gestoppt wird. Also sowas ähnliches wie Sleep, nur daß es sich bloß auf die Stelle beim spielrelevanten Zeug bezieht.
Eine Möglichkeit wäre, das mit Threads zu bewerkstelligen. Der Hauptthread, der das Fenster erstellt und die Messageloop ausführt, benutzt entweder einfach GetMessage, oder PeekMessage und wartet dann mit MsgWaitForMultipleObjects auf Nachrichten.
Der zweite Thread mit der GameLoop limitiert die Framerate mit SetWaitableTimer, womit sich in der Regel eine höhere Auflösung als mit Sleep erreichen lässt, allerdings auch nicht garantiert. Ich vermute mal, dass das wie PerformanceCounter auf neuerer Hardware kein Problem mehr sein dürfte.
Ich würde es ohne Threads machen, und dafür das explizite warten auf Nachrichten weglassen. Also PeekMessage und SetWaitableTimer in der Hauptschleife.
-
Nukularfüsiker schrieb:
Der zweite Thread mit der GameLoop limitiert die Framerate mit SetWaitableTimer, womit sich in der Regel eine höhere Auflösung als mit Sleep erreichen lässt, allerdings auch nicht garantiert. Ich vermute mal, dass das wie PerformanceCounter auf neuerer Hardware kein Problem mehr sein dürfte.
Ja, da haben wir's wieder: Auf neuerer Hardware. Und was ist, wenn der Spieler keine neuere Hardware hat? Meine Spiele selbst wären eher so auf NES-Niveau. Und ich hasse es, wenn jemand solche Minispielchen programmiert und dann einen Hochleistungs-PC verlangt, damit es ordentlich läuft. Zum Beispiel hat mal einer den Story-Modus von "Super Smash Bros." als NES-ähnliches Spiel programmiert. Im Prinzip ein simples Jump'n'Run. Aber das Teil lief nicht, wenn man nicht mindestens 1 GB Arbeitsspeicher hatte. Deshalb achte ich bei meinen Programmen darauf, daß sie auch auf alten PCs laufen. Und wenn nicht garantiert ist, daß SetWaitableTimer besser als Sleep ist, dann laufen meine Spiele nachher auf alten PCs in Zeitlupe ab. Dann braucht man wieder 12 Sekunden, um von links nach rechts zu laufen. So wie ganz früher in meinen ersten Spielen, wo ich von Spielschleifen noch nichts wußte und tatsächlich den WM_TIMER benutzt habe. Nur daß die langsame Geschwindigkeit damals Teil des Spiels war.
Wie wird denn das bei richtigen Spielen gemacht? Es muß doch da irgendein Patentrezept geben. Ich meine, ich sehe nicht, daß jedes Spiel automatisch auf 100 % Auslastung geht. Also muß es doch eine Lösung geben, die dann quasi die Lösung ist.
-
Die benutzen VSync oder Sleep(1).
Edit: neuere Hardware heißt so ab P3 aufwärts. Was hast du denn für eine Zielgruppe, die auf mehr als 10 Jahre alten PCs spielt?
Edit2: AFAIK hat Sleep niemals eine geringere Auflösung als ca. 15 ms. Das wären ~60 Hz. Das ist ja nicht gerade Zeitlupe.
-
Ich hab einfach einen Framelimiter eingebaut:
if ( m_wantsFrameLimit) { while ( m_tmNext > SDL_GetTicks() ) { SDL_Delay(1); } m_tmNext += (1000 / m_fps_limit); }
Damit wird das Bild nicht zu oft neu gezeichnet und es können nebenher andere Funktionen laufen. Ich hab zumindest nicht festgestellt, dass mein System dadurch langsamer wird. Man kann locker nebenher noch Eve Online spielen :P.
Ob da jetzt im Taskmanager 100% steht oder nicht, is doch wayne.
Was ich aber einbauen würde, ist eine Funktion dass das Programm nur arbytet wenn es auch aktiv ist, sprich das Fenster den Fokus hat.edit: Eben nachgesehen, bei mir steht 0% Auslastung mit laufendem Rendering. Hab allerdings noch keine große Logik. Wobei das aber nicht den Prozzi auf 100 treiben sollte :p
lg
-
Nukularfüsiker schrieb:
Die benutzen VSync oder Sleep(1).
Ist das wirklich die korrekte Herangehensweise: Stets und ständig das gesamte Programm, inklusive der GUI, mit Sleep zu stoppen? (Ich denke nämlich nicht, daß in Spielen jeweils ein extra Thread erstellt wird.)
Und VSync hat doch was damit zu tun, wann das Bild aktualisiert wird. Aber wie ich schon sagte: Mein Problem ist nicht die Auslastung, die für das Zeichnen des Bildes gebraucht wird, sondern die Auslastung, die gegeben ist, wenn nichts passiert. Was also hat VSync mit meinem Problem zu tun, daß einwhile (true) { }
pauschal 100 % Auslastung hat?
Nukularfüsiker schrieb:
Edit: neuere Hardware heißt so ab P3 aufwärts. Was hast du denn für eine Zielgruppe, die auf mehr als 10 Jahre alten PCs spielt?
Nun, die Möglichkeit ist immer gegeben.
Nukularfüsiker schrieb:
Edit2: AFAIK hat Sleep niemals eine geringere Auflösung als ca. 15 ms. Das wären ~60 Hz. Das ist ja nicht gerade Zeitlupe.
Stimmt. Ich hab es ausprobiert. Trotzdem finde ich, ist das eine Nothilfe, da ja eben wirklich das gesamte Programm angehalten wird, was ich für unsauber halte.
L33TF4N schrieb:
Ich hab einfach einen Framelimiter eingebaut
Nochmal: Mein Problem ist nicht die Auslastung beim Zeichnen, sondern die Auslastung zwischen zwei Zeichenvorgängen. Ein Frame Limiter bringt da gar nichts, weil schon allein dieser Code:
clock_t lastClock = 0; do { if ((clock() - lastClock) * 1000 / CLOCKS_PER_SEC > IrgendeinWert) { // DrawScreen() lastClock = clock(); } while (gameIsRunning);
100 % Auslastung bringt, auch wenn es gar kein DrawScreen gibt.
Daß Dein Programm keine große Auslastung hat, liegt daran, daß Du SDL_Delay benutzt. Was im wesentlichen das gleiche ist wie Sleep. Und das wollte ich nicht verwenden, weil das nicht nur das Spiel selbst anhält, sondern auch das drumherumliegende Programm. Das heißt, die gesamte Nachrichtenschleife wird lahmgelegt. Das mag in der Praxis zwar keinen großen Unterschied machen, aber ich halte es eben für unsauberen Programmierstil.
-
NES-Spieler schrieb:
Ist das wirklich die korrekte Herangehensweise: Stets und ständig das gesamte Programm, inklusive der GUI, mit Sleep zu stoppen?
Es sind bis jetzt fünf verschiedene Möglichkeiten genannt worden: Sleep, WM_TIMER, WaitableTimer, VSync und Polling. Jede hat ihre Vor- und Nachteile, aber keine ist korrekter als die andere. Viel mehr Möglichkeiten wird es kaum noch geben, mir ist zumindest keine gängige bekannt.
Die begrenzte Auflösung von Zeitnehmern auf alter Hardware ist nun mal harter Fakt und nicht zu ändern.(Ich denke nämlich nicht, daß in Spielen jeweils ein extra Thread erstellt wird.)
Ich maße mir nicht an, über die Gesamtheit der Spiele eine Aussage zu treffen, aber es wird bestimmt hier und da gemacht.
Was also hat VSync mit meinem Problem zu tun, daß ein
while (true) { }
pauschal 100 % Auslastung hat?
Ich verstehe das Problem irgendwie nicht. Natürlich nimmt diese Schleife alle dem Thread zugeteilten CPU-Ressourcen in Anspruch. Wenn du das nicht willst, musst du den Thread schlafen legen. Entweder für eine bestimmte Zeitspanne oder bis ein bestimmtes Ereignis eintritt. Wie soll sich denn deiner Meinung nach die Lösung nach der du suchst bei dieser Schleife verhalten?
Bei VSync ist das abzuwartende Ereignis der nächste Bildaufbau. Die Wartezeit darauf verbraucht keine CPU-Leistung und man ist auch nicht auf "moderne" Timerhardware angewiesen.
Trotzdem finde ich, ist das eine Nothilfe, da ja eben wirklich das gesamte Programm angehalten wird, was ich für unsauber halte.
Entweder der Thread wartet auf irgendwas oder er wartet nicht. Du musst definieren, worauf denn nun gewartet werden soll. Und wenn dabei nicht das gesamte Programm warten soll, musst du mehrere Threads benutzen.
-
NES-Spieler schrieb:
Und VSync hat doch was damit zu tun, wann das Bild aktualisiert wird.
Ja. Allerdings wenn du zum Beispiel
IDirect3DDevice9::Present
aufrufst und VSync an ist, dann blockiert die Methode bis gezeichnet werden konnte.NES-Spieler schrieb:
Nukularfüsiker schrieb:
Edit: neuere Hardware heißt so ab P3 aufwärts. Was hast du denn für eine Zielgruppe, die auf mehr als 10 Jahre alten PCs spielt?
Nun, die Möglichkeit ist immer gegeben.
Bietest du auch Unterstützung für MS-DOS an? Nein? Wieso nicht? Aber nicht etwa, weil es zu alt ist? Könnte ja sein, dass es da draussen irgendwo noch einen Nutzer hat. Mal im Ernst: Du machst dir das Leben schwer und hast keinen Nutzen davon.
Grüssli