Таймеры в Clojure?

Мне интересно, есть ли какое-либо широко распространенное решение таймера.

Я хочу написать простой игровой движок, который будет обрабатывать ввод пользователей каждые 20 мс (или выполнять некоторые действия один раз после 20ms (или любой другой период)) и в основном обновлять "глобальное" состояние через транзакции, а также я планирую использовать futures таким образом, это решение должно иметь возможность иметь дело с предостережениями параллелизма.

вы можете дать мне совет?

3 ответов


у вас на самом деле есть две разные проблемы.

во-первых, это вопросы таймеры. У вас есть много вариантов здесь:

  • запустить поток, который спит между действиями, что-то вроде (future (loop [] (do-something) (Thread/sleep 20) (when (game-still-running) (recur))))
  • используйте Java TimerTask - легко позвонить из Clojure
  • используйте библиотеку, как моя маленькая утилита задание это включает в себя DSL для повторения задач
  • используйте функцию таймера из любого игрового движка, который вы используете-большинство из них предоставляют некоторые инструменты для настройки игрового цикла

Я бы, вероятно, просто использовал простой вариант потока - его довольно легко настроить и легко взломать больше функций позже, если вам нужно.

второй вопрос обработка состояния игры. Это на самом деле более сложная проблема, и вам нужно будет разработать ее вокруг конкретного типа игры, которую вы делаете, поэтому я просто дам несколько очков совет:

  • если состояние игры неизменный, то вы получаете много преимуществ: ваш код рендеринга может рисовать текущее состояние игры независимо, в то время как обновление игры вычисляет следующее состояние игры. Неизменность имеет некоторые затраты на производительность, но преимущества параллелизма огромны, если вы можете заставить его работать. Обе мои мини-игры Clojure (Ironclad и Alchemy) используют этот подход
  • вы, вероятно, должны попытаться сохранить свое состояние игры в одиночный var верхнего уровня. Я считаю, что это работает лучше, чем разделение разных частей игрового состояния на разные vars. Это также означает, что вам действительно не нужны транзакции на основе ref: атом или агент обычно делают трюк.
  • вы можете реализовать очередь которые должны обрабатываться последовательно функцией обновления состояния игры. Это особенно важно, если у вас несколько параллельных источников событий (например, действия игрока, таймер клещи, сетевые события и т. д.)

В настоящее время я бы счел core/async хорошим выбором, так как

  • Он очень хорошо масштабируется, когда дело доходит до сложных задач, которые могут быть разделены на действия, которые общаются с помощью каналов
  • избегает привязки активности к потоку

вот эскиз

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

Это решение предполагает, что вы пишете Clojure на JVM. Что-то вроде этого может сработать:

(import '(java.util TimerTask Timer))

(let [task (proxy [TimerTask] []
             (run [] (println "Here we go!")))]
  (. (new Timer) (schedule task (long 20))))