Какова основная структура Netwire 5?

Я пытаюсь проникнуть в Netwire, я копался в поисках документации, вводных материалов, руководств и многого другого, но почти каждый учебник и существующий код устарели по сравнению с Netwire 5 и используют функции из Netwire 4, которых больше нет. нас. README.md">README может быть полезен, но не все компилируется, и тем не менее он едва предоставляет достаточно информации для получения началось.

Я прошу объяснения или пример только для того, чтобы запустить игровой цикл и иметь возможность реагировать на события, поэтому я ищу информацию, чтобы в конечном итоге узнать:

  1. Базовая структура (например, как в реактивном банане вы активируете сетевые описания, которые используют обработчики, определяют поведение и реагируют на события).
  2. Как это в конечном итоге происходит в main.
  3. Как обрабатывать события ввода-вывода (например, щелчок мышью, нажатие клавиши или обратный вызов игрового цикла), как события поступают в сеансы и т. д.

И еще что-нибудь актуальное.

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

Благодарю вас!


person MasterMastic    schedule 16.08.2014    source источник


Ответы (2)


Отказ от ответственности: мне не удалось найти каких-либо крупномасштабных программ, использующих Netwire, поэтому все, что я собираюсь написать, вы должны воспринимать с долей скептицизма, так как это основано на моем собственном опыте использования Netwire. Используемые здесь примеры в основном взяты из моей собственной библиотеки и попытка написать игру с использованием FRP и может быть "неправильным способом" что-то делать.


Вопрос 1: базовая структура (например, как в реактивном банане вы активируете сетевые описания, которые потребляют обработчики, определяют поведение и реагируют на события).

Сеансы. Автор библиотеки netwire дал действительно хороший ответ. о базовой структуре сетевой программы. Поскольку он немного устарел, я обрисую здесь некоторые моменты. Прежде чем мы рассмотрим провода, давайте сначала посмотрим, как netwire обрабатывает время, лежащий в основе драйвера FRP. Единственный способ ускорить время без использования тестовой системы testWire — создать Session, который будет возвращать временные дельты с сохранением состояния. Способ Sessions сохранения состояния инкапсулирован в их тип:

newtype Session m s = Session { stepSession :: m (s, Session m s) }

Здесь Session находится внутри монады (обычно IO) и каждый раз, когда она оценивается, возвращает значение "временного состояния" типа s и новое Session. Обычно любое полезное состояние s может быть записано как значение Timed, которое может возвращать некоторый экземпляр Real t:

data Timed t s
class (Monoid s, Real t) => HasTime t s | s -> t where
    -- | Extract the current time delta.
    dtime :: s -> t
instance (Monoid s, Real t) => HasTime t (Timed t s)

Например, в играх вы обычно требуете фиксированный временной шаг для выполнения вашего обновить звонки. netwire кодирует это понятие с помощью:

countSession_ :: Applicative m => t -> Session m (Timed t ())

countSession_ принимает в качестве входных данных временной шаг, в данном случае фиксированное значение типа t, и создает Session, значения состояния которого имеют тип Timed t (). Это означает, что они кодируют только одно значение типа t и не содержат никакого дополнительного состояния со значением (). После того, как мы обсудим провода, мы увидим, как это влияет на их оценку.

Провода. Основной тип "провода" в Netwire:

Wire s e m a b

Этот провод описывает значение reactive типа b, которое выполняет следующие действия:

  • Принимает на вход реактивное значение типа a
  • Действует внутри Монады m
  • Может запрещать или не создавать значение, давая значение запрета типа e
  • Принимает состояние времени, заданное s

В силу того, что провода являются реактивными величинами, их можно рассматривать как функции, изменяющиеся во времени. Следовательно, каждый проводник кодируется как функция времени (или состояния времени s), что создает в данный момент времени новое значение типа b и новый проводник для оценки следующего ввода типа a. Возвращая значение и новый проводник, функция может охватывать состояние, распространяя его через определения функций.

Кроме того, проводники могут запрещать или не создавать значение. Это полезно, когда вычисления не определены (например, когда мышь находится за пределами окна приложения). Это позволяет вам реализовать такие вещи, как switch, где провод меняется на другой провод, чтобы продолжить выполнение (например, игрок завершает свой прыжок).

С этими идеями мы можем увидеть основной драйвер проводов в netwire:

stepWire :: Monad m => Wire s e m a b -> s -> Either e a -> m (Either e b, Wire s e m a b)

stepWire wire timestate input делает именно то, что мы сказали ранее: он принимает wire и передает ему текущие timestate и input из предыдущего провода. Затем в базовой монаде m он либо выдает значение Right b, либо запрещает значение Left e, а затем дает следующий провод для использования в вычислениях.

Вопрос 2: Как это в конечном итоге происходит в main.

Вооружившись значениями типа Session и Wire, мы можем построить цикл, который снова и снова делает две вещи:

  1. Шаги сеанса для получения нового состояния времени
  2. Использует новое состояние времени для шага по проводу

Вот пример программы, которая навсегда изменяет фиксированный счетчик, чтобы считать двойками:

import Control.Wire

-- My countLoop operates in the IO monad and takes two parameters:
--   1. A session that returns time states of type (Timed Int ())
--   2. A wire that ignores its input and returns an Int
countLoop :: Session IO (Timed Int ()) -> Wire (Timed Int ()) () IO a Int -> IO ()
countLoop session wire = do
  (st, nextSession) <- stepSession session
  (Right count, nextWire) <- stepWire wire st (Right undefined)
  print count
  countLoop nextSession nextWire

-- Main just initializes the procedure:
main :: IO ()
main = countLoop (countSession_ 1) $ time >>> (mkSF_ (*2))

Вопрос 3: Как обрабатывать события ввода-вывода (например, щелчок мышью, нажатие клавиши или обратный вызов игрового цикла), как события поступают в сеансы и т. д.

Существует некоторая дискуссия о том, как это сделать. Я думаю, что в этой ситуации лучше всего воспользоваться базовой монадой m и просто передать моментальный снимок текущего состояния функции stepWire. При этом большинство моих входных проводов выглядят примерно так:

mousePos :: Wire s e (State Input) a (Float, Float)

Где ввод в провод игнорируется, а ввод мыши читается из монады State. Я использую State, а не Reader, чтобы правильно обрабатывать отскоки клавиш (чтобы нажатие на пользовательский интерфейс также не нажимало на что-то под пользовательским интерфейсом). Состояние задается в моей функции main и передается в runState, которая также выполняет шаг проводов. Запрещающее поведение проводов, подобных этому, может стать элегантным кодом. Например, предположим, что у вас есть провода right и left для клавиш со стрелками, которые выдают значение, если клавиша нажата, и блокируют в противном случае. Вы можете создать движение персонажа с помощью провода, который выглядит так:

(right >>> moveRight) <|> (left >>> moveLeft) <|> stayPut

Поскольку провода являются экземпляром Alternative, если right запрещает, он просто перейдет к следующему возможному проводу. a <|> b заблокирует только в том случае, если оба a и b заблокируют.

Вы также можете написать свой код, чтобы воспользоваться преимуществами системы Event netwire, но вам придется создавать свои собственные проводники, которые возвращают Event, используя Control.Wire.Unsafe.Event. При этом я еще не нашел эту абстракцию более полезной, чем простое торможение.

person Mokosha    schedule 16.08.2014
comment
если мы когда-нибудь встретимся, я куплю тебе пива за то, что ты напишешь этот ответ - person paul; 24.09.2020

Возможно, уже поздно отвечать на этот вопрос, но у меня есть ответ, содержащий (минималистическую) структуру программы Netwire 5: Интерактивность консоли в Netwire?

person Carl Dong    schedule 29.09.2015