9 Stimmen

Timer in Clojure?

Ich frage mich, ob es eine weit verbreitete Timer-Lösung gibt.

Ich möchte eine einfache Spiel-Engine schreiben, die die Benutzereingabe bei jedem Tick alle 20 ms verarbeitet (oder einige Aktionen einmal nach 20 ms (oder einem anderen Zeitraum) ausführt) und im Grunde den "globalen" Zustand über Transaktionen aktualisiert. Außerdem plane ich, Zukünfte zu verwenden, damit diese Lösung mit den Fallstricken der Nebenläufigkeit umgehen kann.

Kannst du mir einen Rat geben?

16voto

mikera Punkte 103423

Sie haben eigentlich zwei verschiedene Probleme hier.

Das erste sind die Probleme mit Timern. Sie haben hier viele Möglichkeiten:

  • Starten Sie einen Thread, der zwischen Aktionen schläft, etwas wie (future (loop [] (do-something) (Thread/sleep 20) (when (game-still-running) (recur))))
  • Verwenden Sie eine Java TimerTask - einfach von Clojure aus aufzurufen
  • Verwenden Sie eine Bibliothek wie mein kleines Dienstprogramm Task, das eine DSL für sich wiederholende Aufgaben beinhaltet
  • Verwenden Sie die Timer-Funktionalität von dem Spiele-Engine, den Sie verwenden - die meisten von ihnen bieten einige Werkzeuge zur Einrichtung einer Spielschleife

Ich würde wahrscheinlich einfach die einfache Thread-Option verwenden - es ist ziemlich einfach einzurichten und einfach mehr Funktionen später hinzuzufügen, wenn Sie es müssen.

Das zweite Problem ist das Behandeln des Spielzustands. Dies ist eigentlich das kniffligere Problem und Sie müssen es um den speziellen Typ des Spiels herum entwerfen, also gebe ich nur ein paar Ratschläge:

  • Wenn Ihr Spielzustand unveränderlich ist, erhalten Sie viele Vorteile: Ihr Rendercode kann den aktuellen Spielzustand unabhängig zeichnen, während das Spielupdate den nächsten Spielzustand berechnet. Unveränderlichkeit hat einige Leistungskosten, aber die Vorteile der Nebenläufigkeit sind enorm, wenn Sie es zum Laufen bringen können. Beide meine Mini-Clojure-Spiele (Ironclad und Alchemy) nutzen diesen Ansatz
  • Sie sollten wahrscheinlich versuchen, Ihren Spielzustand in einer einzigen top-Level-Var zu speichern. Ich finde, dass dies besser funktioniert als das Aufteilen von verschiedenen Teilen des Spielzustands über verschiedene Vars. Es bedeutet auch, dass Sie im Grunde keine Ref-basierten Transaktionen benötigen: ein Atom oder Agent wird normalerweise ausreichen.
  • Sie möchten möglicherweise eine Warteschlange von Ereignissen implementieren, die sequenziell vom Spielzustands-Update-Funktion verarbeitet werden müssen. Dies ist besonders wichtig, wenn Sie mehrere gleichzeitige Ereignisquellen haben (z.B. Spieleraktionen, Timer-Ticks, Netzwerkevents usw.)

7voto

Heutzutage würde ich core/async als eine gute Wahl betrachten, da

  • es sehr gut skaliert, wenn es um komplexe Aufgaben geht, die in Aktivitäten unterteilt werden können, die über Kanäle kommunizieren
  • es vermeidet, Aktivität an einen Thread zu binden

hier ist die Skizze

(require '[clojure.core.async :refer [go-loop]])
(go-loop []
  (do
    (etwas tun ...)
    (Thread/sleep 1000)
    (recur))))

6voto

Benjamin Gruenbaum Punkte 259076

Diese Lösung geht davon aus, dass Sie Clojure auf der JVM schreiben. So könnte es funktionieren:

(import '(java.util TimerTask Timer))

(let [task (proxy [TimerTask] []
             (run [] (println "Los geht's!")))]
  (. (new Timer) (schedule task (long 20))))

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X