Seite 1 von 2

Thread: Semaphore und Mutexe

Verfasst: 08.09.2013 18:42
von Chimorin
Heyho,

könnten erfahrene Threadspezialisten bitte mal kurz über diesen Code schauen? Ich habe ihn aus der Hilfe zusammengeschustert ^^
Soll für ein Spiel sein, d.h. die Keyboardreleased werden dann zu Spielerbewegungen,... verarbeitet.

Bitte alles, was irgendwie nicht sauber programmiert ist sofort melden; Ich würde gerne wissen, ob das so gut ist oder ob es Probleme geben könnte (Speziell beim Durchlaufzähler im Thread).

Falls ihr den Code auch ausprobieren wollt, speichert ihr ihn einfach im 3D Examples Ordner.

Code: Alles auswählen

EnableExplicit


Structure FPS
  zeit.i
  zaehler.i
EndStructure


Define.FPS FPS
Define.i frameZeit, Quit
Global Semaphore = CreateSemaphore()
Global Mutex = CreateMutex()


Global NewList Queue.i()


;Fenster
Enumeration
  #Window3D_0
EndEnumeration


;Gadgets
Enumeration
  #Text3D_0
EndEnumeration


;Kameras
Enumeration
  #kamera_0
EndEnumeration


;Events
Enumeration
  #Escape_Up
  #A_Up
  #S_Up
EndEnumeration


Procedure Locker(event.i)
  If TryLockMutex(Mutex)        ;Wenn nicht erfolgreich, verarbeitet Hauptschleife gerade Eingaben; Ein bisschen Schwund ist Standart :D
    LastElement(Queue())
    AddElement(Queue())
    Queue() = event
    UnlockMutex(Mutex)    
    
    ; Signalisiere, dass es ein neues Queue-Element gibt
    SignalSemaphore(Semaphore)
  EndIf
EndProcedure


Procedure InputHandler(nr.i)
  Protected.FPS FPS
  
  Repeat
    FPS\zaehler = FPS\zaehler + 1       ;Zählt die vergangenen Frames
    
    If FPS\zaehler > 200        ;FPS-Grenze bei 200 FPS, die Eingaben dürfen ruhig schneller sein.
      Delay(FPS\zaehler - 200)
      ;Else
      ;Hier kann man dann einen Verzug an den Server senden.
    EndIf
    
    If ElapsedMilliseconds() - FPS\zeit >= 1000       ;Damit werden die Frames pro Sekunde (1000ms) ermittelt
      FPS\zeit = ElapsedMilliseconds()
      FPS\zaehler = 0
    EndIf
    
    ExamineKeyboard()
    ExamineMouse()
    
    InputEvent3D(MouseX(), MouseY(), MouseButton(#PB_MouseButton_Left))
    
    If KeyboardReleased(#PB_Key_Escape)
      Locker(#Escape_Up)
    EndIf
    
    If KeyboardReleased(#PB_Key_A)
      Locker(#A_Up)
    EndIf
    
    If KeyboardReleased(#PB_Key_S)
      Locker(#S_Up)
    EndIf
    
  ForEver
EndProcedure


Procedure Window3D()
  OpenWindow3D(#Window3D_0, 10, 10, 400, 400, "Threadtest")
  TextGadget3D(#Text3D_0, 10, 10, 380, 380, "")
EndProcedure


InitEngine3D()
InitSprite()
InitKeyboard()
InitMouse()


Add3DArchive("Data/GUI", #PB_3DArchive_FileSystem)

OpenScreen(1920, 1080, 32, "Threadtest")
CreateCamera(#kamera_0, 0, 0, 100, 100)
Window3D()

CreateThread(@InputHandler(), -1)

Repeat
  
  FPS\zaehler = FPS\zaehler + 1       ;Zählt die vergangenen Frames
  
  If FPS\zaehler > 100        ;FPS-Grenze bei 100 FPS
    Delay(FPS\zaehler - 100)
    ;Else
    ;Hier kann man dann einen Verzug an den Server senden.
  EndIf
    
  If ElapsedMilliseconds() - FPS\zeit >= 1000       ;Damit werden die Frames pro Sekunde (1000ms) ermittelt
    FPS\zeit = ElapsedMilliseconds()        ;Zeit, zu der diese
    FPS\zaehler = 0
  EndIf
  
  
  While TrySemaphore(Semaphore)
    If TryLockMutex(Mutex)
      FirstElement(Queue())
      Select Queue()
        Case #Escape_Up
          Quit = #True
          
        Case #S_Up
          SetGadgetText3D(#Text3D_0, GetGadgetText3D(#Text3D_0) + "s")
          
        Case #A_Up
          SetGadgetText3D(#Text3D_0, GetGadgetText3D(#Text3D_0) + "a")
          
      EndSelect
      DeleteElement(Queue())
      UnlockMutex(Mutex)
    Else
      SignalSemaphore(Semaphore)
    EndIf
  Wend

  
  
  frameZeit = RenderWorld(frameZeit)        ;Die Physik wird mit der Zeit des vorletzten Frames gefüttert, als Ergebnis die Zeit des letzten Frames.
  
  FlipBuffers()
Until Quit
End
Gruß

Banane

Re: Thread: Semaphore und Mutexe

Verfasst: 08.09.2013 19:11
von NicTheQuick
Ich verstehe das mit dem Delay() nicht. Also falls die Schleife nach einer Sekunde öfter als 200 Mal durchlaufen wurde, wird ein Delay gemacht mit der Anzahl der Schleifendurchläufe minus 200 in Millisekunden? Wie kommst du darauf die Schleifendurchläufte plötzlich auf Millisekunden zu mappen? Und dann wird das Delay auch immer nur im Sekundentakt gemacht.
Es könnte folgendes passieren: 1000 ms sind rum und der Zähler hat bis 2000 gezählt. Jetzt wird das If ausgeführt und da der Zähler schon bei 2000 ist, werden 1800 ms gewartet.

Ach nee, moment. Vor dem "If FPS\zaehler > 200" steht ja noch "FPS\zaehler = 0". Okay, dann passiert das Problem zwar nicht, aber dann ist auch das "If" auch echt unnötig. <)

Ansonsten fällt mir noch ein Problem auf. Dein "While TrySemaphore(Semaphore)" ist zunächst ja korrekt so, aber wenn direkt danach zufällig der Mutex nicht gelockt werden kann, dann verlierst du ja ein Event. Es ist dann zwar noch in der LinkedList drin, aber du kriegst kein SignalSemaphore mehr dafür. Statt dem 'TryLockMutex' solltest du also 'LockMutex' nutzen.

Re: Thread: Semaphore und Mutexe

Verfasst: 08.09.2013 19:15
von STARGÅTE
Erst mal, warum verwendest du hier einen Thread?
Hat in meinen Augen wenig sinn. Es wäre einfach wenn du die Funktion InputHandler() direkt aufrufst, natürlich in abgeänderter form.

Zum Code:

Code: Alles auswählen

While TrySemaphore(Semaphore)
    If TryLockMutex(Mutex)
Das geht so nicht, dadurch könnte dein Semaphore runter zählen, aber wenn der Mutex nicht frei ist, wird das Event garnicht verarbeitet und dann nie mehr.

Edit: Immer wieder erstaunlich wie Inhaltlich ähnlich unsere Antworten sind NicTheQuick

Re: Thread: Semaphore und Mutexe

Verfasst: 09.09.2013 08:37
von Chimorin
Vielen Dank für die Antworten.

Den Code für den FPS-Zähler habe ich von einem User namens adra (Development-lounge). So müsste das doch funktionieren (So war das von ihm glaube ich auch gedacht...).

Das TryLockMutex() wurde durch ein Else mit SignalSemaphore() erweitert, Problem behoben.

Im Thread wird jetzt nicht gewartet, bis Zugriff vorhanden ist; Richtig oder Falsch?

@STARGÅTE:
Wie macht ein Thread dann Sinn?
Wenn das Spiel anfängt wegen RenderWorld() zu ruckeln (Schlechter PC, zu viele Batches, zu viele Tris,...), dann wird wenigstens die Tastatur und Mauseingabe noch verarbeitet (Ich glaube jetzt allerdings, dass das nicht wirklich ein Totschlagargument ist).
Ich würde 2 Threads für einen Mehrspielermodus benutzen, für das Empfangen und Senden der Daten (Oder nach TCP und UDP getrennt?!).
Wofür kann ich Threads benutzen? In meinem Mapeditor gibt es einen kurzen Abfall der Framerate, wenn Terrain Stücke nachgeladen werden. Kann ich diese etwa in einem oder mehreren Threads laden lassen? Oder gibt es da wieder Probleme mit halb fertigen Terrains beim nächsten RenderWorld()?

Re: Thread: Semaphore und Mutexe

Verfasst: 09.09.2013 13:02
von NicTheQuick
Wenn du schon die Game- von der Render-Engine trennen willst, dann auch bitte ganz. Soll heißen in dem Thread, wo die Eingaben aufgenommen werden, sollten auch die Spieler bewegt und die Physik berechnet werden.
Sonst macht es wenig Sinn. Gerade der Befehl 'KeyboardReleased()' gibt ja nur #True zurück, wenn die Taste seit dem letzten Mal gedrückt und wieder losgelassen wurde. Ob du das dann im Thread prüfst oder den Befehl einfach in der Hauptschleife aufrufst, macht dabei keinen Unterschied. Bei 'KeyboardPushed()' macht das schon mehr Sinn, aber auch nur, wenn die Bewegungen und die Physik dann auch in dem selben Thread abgearbeitet werden.
Würden sie erst abgearbeitet werden, wenn sie in der Hauptschleife ankommen, dann könntest du das 'KeyboardPushed()' auch genau so gut da hin machen.

Re: Thread: Semaphore und Mutexe

Verfasst: 09.09.2013 13:58
von STARGÅTE
Ein Thread mach dann sinn, wenn man eine Procedure hat, die sehr viel Zeit in Anspruch nimmt (einige Sekunden) und diese aber gerne im "Hintergrund" oder Parallel zum Haupprogramm laufen lassen möchte.

Ein klassisches Beispiel (wie ich finde) ist OpenNetworkConnection().
Diese Funktion benötigt schon mal einige Sekunden, gerade dann wenn keine Verbindung hergestellt werden kann.
Damit das Hauptprogramm (zB ein Screen mit einem Ladekreis) aber nicht pausiert wird, sondern weiter die Animation des Ladekreises anzeigt, wäre es hier geeignet OpenNetworkConnection() in einen Thread auszulagern.

In deinem Beispiel hast du keine solche Zeitintensive Funktion.
Und wie NicTheQuick auch schon sagte, ist es auch quatsch zwar KeyboardReleased() zu registieren, aber trotzdem bei LAG nur in jedem Frame zu verarbeiten.

Auch wirst du nicht durch schnelles drücken und loslassen eine höhere Frequenz erreichen als deine Framerate.
Und wenn es LAGt will ich sowieso nicht weiter spielen ^^

Viel wichtier ist, dass Objekte die sich bewegen, immer die gleiche Geschwindigkeit haben, unabhängig von der Framerate.
Ein einfaches x+v geht also nicht, sondern hier muss x+v*dt gerechnet werden, wobei dt die (durchschnitts) Zeit zwischen zwei Frames ist. Dann benötigst du auch keine Frame-Bremse, wie du sie aktuell eingebaut hast.

Re: Thread: Semaphore und Mutexe

Verfasst: 09.09.2013 16:50
von Chimorin
Die Framebremse habe ich nur drin, damit die CPU-Nutzung nicht auf 100% springt...

Hmm, ok. Zeitintensive Sachen. Da zählt wohl das Nachladen von Terrainstücken dazu. Ich probiere das mal aus; Mal sehen, ob es Komplikationen gibt :D

Re: Thread: Semaphore und Mutexe

Verfasst: 09.09.2013 22:30
von PMV
Noch 2 ... 3 Dinge die du beachten solltest:
1. Alles, was mit PB-OGRE zu tun hat, ist nicht Threadsicher.
Da zählt auch das InputEvent3D() dazu.

2. Da die PB-Linked-Listen nie mals in 2 Threads gleichzeitig
verwendet werden können, sind diese komplett ungeeignet
als Queue zum Austausch von Events zwischen Threads.
Nic hat ja letztens erst ne schön saubere Variante als Modul
gepostet. PB bringt beim Thema Threads leider recht wenig mit.

3. Nimm besonders viel Abstand von unsauberen Implementierungen
Wenn du schon bei der Entwicklung weist, das etwas nicht 100%
funktioniert, dann such eine Alternative! Die beste Spielidee bringt
gar nichts, wenn durch unsaubere Arbeit der Spielspaß bereits im
Keim erstickt wird. Die Steuerung in einen Thread aus zu lagern
macht man deswegen, damit das Spiel sauber darauf reagieren kann.
Dann Eingaben aber bewusst zu verschlucken ... lol! :?

MFG PMV

Re: Thread: Semaphore und Mutexe

Verfasst: 10.09.2013 10:42
von Chimorin
PMV, wie meinst du "sauber darauf reagieren"?

Ich werde wohl die Maus- und Tastatursachen in der Hauptschleife lassen und nichts davon in einen Thread stecken.

EDIT: Ich muss mich korrigieren. Die CPU-Nutzung schnellt nicht nach oben <)

Re: Thread: Semaphore und Mutexe

Verfasst: 11.09.2013 21:55
von PMV
http://forums.purebasic.com/german/view ... =4&t=25908

PS: genau so gibt es auch Tastaturen, die besonders hohe Abtastraten haben.
Der Aufwand dafür ist etwas höher und ob sich das wirklich auszahlt ... wohl
eher nur wenn man einen Titel hat, der im E-Sport benutzt wird. :?
Aber sauber ist sauber und mit der Event-Queue hast du auch den richtigen
Ansatz. :wink:

MFG PMV