Input-Handling synchron mit Logik?



  • Hallo zusammen,

    Ich habe mich in letzter Zeit nach Fix Your Timestep gerichtet, was Game Loops angeht. Nun wollte ich fragen, ob ihr die Benutzereingabe so einrichten würdet?

    while (running)
    {
    	accumulator += frametime();
    	while (accumulator > constant)
    	{
    		accumulator -= constant;
    
    		input();
    		update(constant);
    	}
    
    	render();
    }
    

    Dabei würde Input (Events und Realtime) also jeweils vor den Logikupdates gesampelt. Macht es unter gewissen Umständen Sinn, input() in die äussere Schleife zu nehmen und damit keine konstante Samplerate mehr zu haben? Üblicherweise ist der Rechenaufwand für Benutzereingaben ja recht gering.

    Mit Multithreading würde man die Funktionalitäten wahrscheinlich schön aufteilen, aber wie siehts mit einem Thread aus?



  • Ich würde es auch so machen. Denkbar wäre eigentlich auch, den Gamestate zu kopieren* dann vor der Schleife alle Renderbefehle los zu werden, und das swap_buffers an's Ende zu schreiben. OpenGL funktioniert ja im Prinzip schon asynchron, und swap_buffers synct dann halt.
    *nach etwas Überlegung betrifft das ja nur ein paar Quaternionen, Vectoren und vielleicht Attribute, und das auch nur von den Dingen die sichtbar sind. Texturen etc. sind für den Logikteil ja eh nicht besonders interessant - normalerweise benutzt man ja auch unterschiedliche Models für Collision Detection und zum Rendern.


  • Mod

    aber welchen sinn machen inputs auf die man garnicht reagiert? natuerlich kannst du in einem extra threads 1000mal pro sekunde den input status abfragen, aber wenn du dann 10fps hast, wird niemand einen unterschied zu 10 abfragen pro sekunde merken nehm ich an.

    du hast grundsaetzlich ein problem wenn die framerate zu klein ist, wenn du hingegen 60fps hat, macht es keinen fuehlbaren unterschied solange du input nicht verschluckst.

    hypothetisch ist natuerlich eine hoehere abtastrate besser und es gibt vielleicht leute die das sogar in der praxis erfuehlen koennen (ich merke auch den unterschied ob ich 120hz oder 60hz updates habe obwohl mein auge eigentlich nur 50hz maximal sehen sollte), aber meistens macht es kein unterschied.

    vielleicht sagst du was fuer eine art von spiel du machst, das hilft vielleicht beim entscheiden.



  • Danke für die Antwort.

    Warum würdest du vor der inneren Schleife rendern und erst nachher anzeigen? Obwohl der Unterschied wahrscheinlich nicht sichtbar ist, hätte man so ja jeweils veraltete Zustände gezeichnet, da zwischen dem Rendern und SwapBuffer Updates sein können.



  • Danke auch dir, rapso. Das stimmt wohl, häufiger zu sampeln macht also normalerweise keinen Sinn.

    Im Moment handelt es sich um ein simples 2D-Spiel, aber ich würde auch gerne das "richtige" Vorgehen für grössere Projekte kennen. Würdest du bei Multithreading input() und update() trotzdem in einen Thread zusammenfassen? Wenn du die beiden gleich häufig aufrufst, macht ja Synchronisierung nicht viel Sinn (ausser Input wäre langsam).



  • Nexus schrieb:

    Warum würdest du vor der inneren Schleife rendern und erst nachher anzeigen?

    Vielleicht habe ich da ein falsches Verständnis für OpenGL (rapso klärt mich sicher auf), aber soweit ich weiß, ist OpenGL zu einem gewissen Grad "Job basiert". Heißt, wenn du glRenderXY aufrufst, rendert er das nicht zwangsläufig sofort, sondern packt es sozusagen in eine job-queue. Ich denke dann sollte klar sein, warum ich vor der Schleife rendern will: Damit rendert man quasi gleichzeitig mit den Logikschritten, und am Ende synchronisiert man.



  • Nexus schrieb:

    DWürdest du bei Multithreading input() und update() trotzdem in einen Thread zusammenfassen? Wenn du die beiden gleich häufig aufrufst, macht ja Synchronisierung nicht viel Sinn (ausser Input wäre langsam).

    ich sehe keinen nuetzlichen grund es nicht im main loop zu machen.
    -die abfrage vom input dauer weniger lange als thread <-> thread kommunikation
    -wenn du 1fps hast, hast du mit einem extra thread natuerlich bessere kontrolls als wenn das spiel nur einmal pro sekunde den input abfragt und dann annimmmt der spieler haette das 1s lang gemacht was er gerade macht, aber bei 1fps macht der input das wenigste aus
    -wenn die fps gut ist, z.b. 120Hz, bist du bei ca 8ms pro frame, threads bei windows werden mit 10ms-20ms gescheduled, damit also der input sauber abtastet, muestest du den thread die ganze zeit voll laufen lassen und 99.9% der zeit wuerde der nichts sinnvolles machen. legst du den in suspend (mit sleep or conditions oder...), laeuft die logik mal 2mal ohne das der input thread dran war, mal kommt der input thread vielleicht 2mal dran. ist nicht so optimal.

    ich wuerde den thread also so lassen wie er ist. in dos zeiten waren input 'threads' (also eigentlich interrupts) gaengig, weil du dann z.b. strategiespiele hattest die mit 5-20 fps liefen, was recht treage war, aber die hardware hatte extra register (also unter VGA/SVGA), um den cursor zu steuern, das konnte man dann mit jeder bewegung der maus machen. ich denke daher kommt noch die idee von einem input thread, denn da gab es ja noch wirklich feedback, cursor mit 60fps zu bewegen fuehlt sich gut an, mit 10fps bei bis 200ms latenz sehr schwammig.

    cooky451 schrieb:

    Nexus schrieb:

    Warum würdest du vor der inneren Schleife rendern und erst nachher anzeigen?

    Vielleicht habe ich da ein falsches Verständnis für OpenGL (rapso klärt mich sicher auf), aber soweit ich weiß, ist OpenGL zu einem gewissen Grad "Job basiert". Heißt, wenn du glRenderXY aufrufst, rendert er das nicht zwangsläufig sofort, sondern packt es sozusagen in eine job-queue. Ich denke dann sollte klar sein, warum ich vor der Schleife rendern will: Damit rendert man quasi gleichzeitig mit den Logikschritten, und am Ende synchronisiert man.

    an sich hast du recht, dafuer gibt es dann glFlush

    while(true)
    {
    render();
    glFlush();
    input();
    logik();
    glFinish();//unter windows macht das wglswapbuffers implizit, ist also nicht noetig
    SwapBuffers();
    }
    

    jedoch buffern treiber heutzutage oft sowieso, weil die GPUs sonst nach dem swap idle waeren, wenn leute logik abarbeiten um dann nach paar ms erst dran zu kommen und das koennen die durch buffern halt leicht umgehen. (im nvidia treiber kann man das glaube ich fuer opengl einstellen, frueher von 0 bis 5frames, heutzutage von 1 bis 5frames, wenn ich mich recht erinnere). ati hatte das mal im treiber, ist mittlerweile aber nicht zuf inden, nichtmal in registry.



  • Okay, danke für die ausführlichen Erklärungen! Dann werde ich input() jeweils direkt vor update() aufrufen.


Anmelden zum Antworten