2

私はまだ簡単なゲームサーバーを書いています。hereからのアドバイスにより、少なくとも2人のプレーヤーが含まれていないときにスレッドをブロックすることを期待して、mvar サポートを実装しました。しかし、そこにデータを入れるまで待ちません。それは常に寝ている Lwt.t を返します。まず最初に、ここで接続を受け入れ、プレイヤーに START に入ってパートナーを探し始めるように勧めます。

let waiting_players = 
    Lwt_mvar.create_empty();;

let rec make_ready player = 
    player >>= fun cli ->
        send_to_client player "Type in START";
        let answer = read_from_client player in 
            answer >>= fun str ->
            match str with
                |"START" -> 
                    let ready_client =  cli in  
                    send_to_client player "Waiting for opponent";
                    Lwt_mvar.put waiting_players ready_client;
                | _ -> 
                    send_to_client player "Unknown command. try again";
                    make_ready player

let handle_income () =
    let in_conection = Lwt_unix.accept sock in 
    in_conection >>= fun (cli, addr) ->
    let player = Lwt.return cli in
    send_to_client player "Welcome to the server. To start game type in START and press Enter";
    make_ready player;;

    val make_ready : Lwt_unix.file_descr Lwt.t -> unit Lwt.t = <fun>
    val handle_income : unit -> unit Lwt.t = <fun>

問題ないようですが、Lwt_mvar.take waiting_players を呼び出すと、前に何も置かれておらず、スレッドがブロックされていなくても、常にいくつかの値が返されます。このような (私にとって) 奇妙な動作は、次の例でよくわかります。

# let bucket = Lwt_mvar.create_empty ();;
val bucket : '_a Lwt_mvar.t = <abstr>

# let apple = Lwt_mvar.take bucket;;
val apple : '_a Lwt.t = <abstr>

# Lwt.state apple;;
- : '_a Lwt.state = Sleep

「ブロッキング」がまさにそのような眠っているオブジェクトを返すことを意味する場合は、教えてください. そして、「準備ができている」オブジェクトのみを返すループを作成する方法が最善の方法ですか? Lwt.is_sleeping を使用するのは良い考えですか? どうもありがとう。

4

2 に答える 2

4

あなたのアプローチにはほとんど問題がなく、コードにはいくつかのバグがあります。したがって、最初に後者を強調し、次に別のアプローチを提案して正当化します。

問題

問題1

send_to_clienttype の値を返すようですunit Lwt.t。式を で終了して無視する場合;は、「メッセージが送信されるまで待たずに先に進む」ことを意味します。通常、これはあなたが望むものではありません。unit Lwt.tそのため、戻り値にバインドして、スレッドが終了するまで待つ必要があります。

問題2

通常、Lwt プログラミングでは、関数は即時型 (つまり、 にラップされていない型) の値を受け入れ、Lwt.t遅延スレッド (つまり、型の値) を返します'some Lwt.t。もちろん、これは通常、あなたが何か違うことをするのを誰も妨げません。ただし、「即時入力、遅延出力」パターンに固執するようにしてください。

問題 3

ツールを使用します。ocp-indentコードをインデントするために使用すると、読みやすくなります。また、コンパイラを使用せず、トップレベルでプレイしているようです。通常、特にシステム プログラミングでは、これは悪い考えです。ocamlbuildコードをコンパイルして実行するには、次を使用します。

ocamlbuild game.native --

ゲーム

OCaml でのプログラミングは、Python やその他の型システムの弱い言語でのプログラミングとは異なる哲学を持っています。OCaml では、型とシグネチャの設計から始めて、後で実装を埋める必要があります。もちろん、これは理想化であり、実際には反復的な洗練のプロセスになりますが、一般的なアプローチは同じです。タイプから始めます。

というわけで、まずはplayer型を定義しましょう。些細なことですが、改善の余地があります。

open Lwt

type player = {
  fd : Lwt_unix.file_descr
}

次に、ゲームの初期化の問題を理解するために、型システムを使用してみましょう。ゲームをプレイする準備ができて喜んでいる 2 人のプレーヤーを用意する必要があります。つまり、3 つの連続した状態があることを意味します。

  • Nobody準備ができています
  • One player準備ができています
  • Both (player1, player2)準備ができています

実際には、3 番目の状態に到達するとすぐにゲームの準備が整うため、その状態は必要ないため、選択肢は 2 つだけになります。

type stage =
  | Nobody
  | One of player

player option選択したものと同型であるため、ここで typeを使用できます。stageしかし、もっと明示的にして、独自の型を使用しましょう。これにより、モデルがより制約され、適合した状態に保たれます。

次のステップは、クライアントとサーバー間の対話のプロトコルを定義することです。requestサーバーからクライアントへresponseのメッセージ、および反対方向に移動するメッセージには名前を使用します。

type request =
  | Init
  | Wait
  | Unknown_command
  | Bye 

type response =
  | Start
  | Quit

このプロトコルは、具体的な表現を含まないという意味で抽象的です。これに基づいて、さまざまな表現 (たとえば、GUI インターフェイスや、さまざまな言語をサポートするテキスト チャット) を構築できます。

しかし、テキストコマンドを使用する最も単純な具体的な実装をモックアップしましょう:

let response_of_string msg =
  match String.trim (String.uppercase msg) with
  | "START" -> Some Start
  | "QUIT" -> Some Quit
  | _ -> None

そして反対方向に(注:このメッセージをクライアント側でレンダリングし、タイプの値を送信することをお勧めします。これにより、トラフィックプロファイルが低く保たれ、さらに重要なことに、さまざまなクライアントを透過requestresponseに接続できるようになります).

let string_of_request = function
  | Init -> "Welcome to a game server.
    Please, type 
    - `start' to start game;
    - `quit' to finish session"
  | Wait -> "Please wait for another player to join the game"
  | Unknown_command -> "Don't understand this"
  | Bye -> "Thank you, see you later!"

次のステップは、 のインターフェイスを定義することIoです。このモジュールは、クライアントとサーバー間の対話を担当します。ソケットや文字列を使用するなど、すべての詳細を抽象化して非表示にする方法に注意してください。

module Io : sig
  val send : player -> request -> unit Lwt.t
  val recv : player -> response option Lwt.t 
end = struct
  let send dst msg = return_unit
  let recv dst = return None
end 

Gameこれで、モジュールを定義できます。最初は、2 つの異なるオートマトンがあります。

  • init2 人のプレイヤー間のゲームを初期化します。
  • playゲームを 1 回プレイすると、犠牲者が 2 人になります。

これをOCamlで明示的に言ってみましょう:

module Game : sig

  (** [play a b] play a game between player [a] and player [b] *) 
  val play : player -> player -> unit Lwt.t

  (** [init next_player] waits until two players are ready to play.
      TODO: Describe a grammar that is recognized by this automaton. *)
  val init : (unit -> player Lwt.t) -> (player * player) Lwt.t
end = struct
  let play a b = return_unit

  let init next_player =
    let rec process stage player = 
      Io.send player Init >>= fun () -> 
      Io.recv player >>= function
      | None ->
        Io.send player Unknown_command >>= fun () ->
        process stage player
      | Some Quit ->
        Io.send player Bye >>= fun () -> 
        next_player () >>= process stage
      | Some Start -> match stage with
        | One a -> return (a,player)
        | Nobody -> 
          Io.send player Wait >>= fun () ->
          next_player () >>= process (One player) in
    next_player () >>= process Nobody
end

これで、すべてを結合する main 関数を書き出すことができます。

let main server_sock = 
  let next_player () =
    Lwt_unix.accept server_sock >>=
    fun (fd,_) -> return {fd} in
  Game.init next_player >>= fun (a,b) -> 
  Game.play a b

このアプローチを続けると、ゲームのさまざまな有限状態マシンがさまざまな言語 (つまり、プロトコル) を定義していることに後で気付くかもしれません。そのため、1 つのプロトコルを使用する代わりに、各 FSM に特定のプロトコル ( 、 など) を使用するinit_protocolことplay_protocolになるかもしれません。これを処理するには、サブタイピングとポリモーフィック バリアントを使用できます。

于 2015-08-28T17:06:23.000 に答える