免責事項: 私は Netwire を使用する大規模なプログラムを見つけることができなかったので、Netwire を使用した私自身の経験に基づいているため、私がこれから書くことはすべて慎重に考えてください。ここで使用する例は、ほとんどが自分のライブラリから取得したもので、 FRP を使用してゲームを作成しようとするものであり、「正しい方法」ではない可能性があります。
質問 1: 基本構造 (reactive-banana で、ハンドラーを消費し、動作を定義し、イベントに反応するネットワーク記述を起動する方法など)。
セッション:
netwire ライブラリの作成者は、netwire プログラムの基本構造について非常に優れた回答を提供しました。少し古いので、ここでいくつかのポイントを概説します。ワイヤを調べる前に、まず、FRP の根底にあるドライバーである netwire が時間を処理する方法を見てみましょう。テスト ハーネスを使用せずに時間を進める唯一の方法は、タイム デルタをステートフルに返すtestWireを生成することです。保存状態がその型にカプセル化されるSession方法:Sessions
newtype Session m s = Session { stepSession :: m (s, Session m s) }
ここで、 aSessionは Monad (通常はIO) 内にあり、評価されるたびに type の「時間状態」値sと newを返しますSession。通常、有用な状態は、 のインスタンスを返すことができる値sとして記述できます。TimedReal 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 ())
AcountSession_は入力としてタイムステップ (この場合は type の固定値) を取り、状態値がtypetである を生成します。これは、 type の単一の値のみをエンコードし、値に追加の状態を持たないことを意味します。ワイヤについて説明した後、ワイヤを評価する際にこれがどのように役割を果たすかを見ていきます。SessionTimed t ()t()
ワイヤ: Netwire の「ワイヤ」の主なタイプは次のとおりです。
Wire s e m a b
このワイヤは、次のことを行うタイプのリアクティブ値を記述します。b
- タイプのリアクティブ値を入力として受け取ります
a
- モナド内で動作する
m
- タイプの阻害値を生成して、値を阻害するか生成しない可能性があります
e
- によって与えられる時間状態を仮定します。
s
リアクティブな値であるという性質上、ワイヤは時変関数と考えることができます。したがって、各ワイヤは時間 (または時間状態s) の関数としてエンコードされ、その時点で type の新しい値と、 typebの次の入力を評価するための新しいワイヤが生成されaます。値と新しいワイヤを返すことにより、関数は関数定義を通じて状態を伝達することで状態を包含することができます。
さらに、ワイヤは値を抑制したり、生成しない場合があります。これは、計算が定義されていない場合 (マウスがアプリケーション ウィンドウの外にある場合など) に役立ちます。switchこれにより、ワイヤーを別のワイヤーに変更して実行を継続する (プレーヤーがジャンプを終了するなど) などを実装できます。
これらのアイデアを使用すると、ネットワイヤのワイヤの主な原因がわかります。
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前のワイヤから渡します。次に、基礎となる Monadmで、 の値を生成するか、 の値でRight b抑制しLeft e、計算に使用する次のワイヤーを提供します。
質問 2: 最終的にどのようにメインになるか。
タイプSessionとの値をWire使用して、2 つのことを繰り返し実行するループを作成できます。
- セッションをステップ実行して、新しい時間状態を受け取ります
- 新しい時間状態を使用してワイヤをステップします
以下は、固定カウンターを変更して永久に 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: IO イベント (マウス クリック、キーダウン、ゲーム ループ コールバックなど) の処理方法、イベントがセッションでどのように発生するかなど。
これをどのように行うかについては、多少の議論があります。mこの状況では、基になる Monad を利用して、現在の状態のスナップショットを関数に渡すのが最善だと思いstepWireます。そうすることで、私の入力ワイヤのほとんどは次のようになります。
mousePos :: Wire s e (State Input) a (Float, Float)
ワイヤーへの入力は無視され、マウス入力はStateモナドから読み取られます。Stateキーのデバウンスを適切に処理するためにand notを使用しReaderます (UI をクリックしても UI の下の何かをクリックしないようにするため)。状態はmain関数で設定され、 に渡されrunStateます。これにより、ワイヤ ステップも実行されます。このようなワイヤの抑制動作は、洗練されたコードを作成できます。たとえば、キーが押された場合に値を生成し、それ以外の場合は禁止するワイヤrightと矢印キーがあるとします。left次のようなワイヤーでキャラクターの動きを作成できます。
(right >>> moveRight) <|> (left >>> moveLeft) <|> stayPut
ワイヤは のインスタンスであるため、禁止されている場合はAlternative、right次の可能なワイヤに移動します。a <|> b両方aとb阻害する場合にのみ阻害します。
netwire のシステムを利用するコードを書くこともできますが、 usingEventを返す独自のワイヤを作成する必要があります。そうは言っても、この抽象化が単純な抑制よりも有用であるとはまだ思っていません。EventControl.Wire.Unsafe.Event