Таймеры в 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))))