Game Servers in OTP

Game Servers in OTP

Given at ElixirConf 2014

64afebe5db598b0b043f35560bf940df?s=128

Martin Schürrer

July 26, 2014
Tweet

Transcript

  1. Game Servers in OTP

  2. @msch featurebranch.com

  3. None
  4. None
  5. None
  6. None
  7. Architecture big-picture The Table-FSM Life of an in-game event Real

    world concerns BoldPoker <3 Elixir Lessons Learned & QA
  8. HA keepalived haproxy active Erlang active Erlang standby Erlang standby

    PostgreSQL haproxy standby
  9. Erlang active

  10. OTP Apps :cardgame :storage :network :gameserver

  11. :cardgame OTP APP {Table,9} {Table,8} {Table,7} {Table,6}

  12. :network OTP APP WebSocket :cowboy Flash (TCP) :ranch

  13. :storage OTP APP PSQLDBWorker TableIdSequence

  14. :gameserver OTP APP {Player,1} {Player,2} {Player,3}

  15. Architecture big-picture The Table-FSM Life of an in-game event Real

    world concerns BoldPoker <3 Elixir Lessons Learned & QA
  16. None
  17. DealCards Knocking DealCards Play Result idle playing

  18. [ {DealCards, [cards_to_deal, :private]}, {Knocking, [5000]}, {DealCards, [cards_to_deal, :private]}, {Negotiation,

    []}, {Play, [contra_enabled: true]}, {Play, []}, {Play, []}, … {Play, []}, {Play, []}, {Result, []} ]
  19. gen_fsm:send_event/2 -----> Module:StateName/2 gen_fsm:send_event/2 -----> Table.playing/2 {:next_state, next_state_name, new_state_data} !

    {:stop, reason, new_state_data}
  20. DealCards Knocking DealCards Play Result idle playing

  21. {:next_stage, new_stage_data, new_state_data} ! {:end_game, new_stage_data, new_state_data} ! Table.broadcast_event {:next_state,

    next_state_name, new_state_data} ! {:stop, reason, new_state_data} ! GenServer.reply Table.playing/2 -----> DealCards.playing/3
  22. DealCards Knocking DealCards Play Result idle playing

  23. Knocking DealCards Play Result idle playing

  24. DealCards Play Result idle playing

  25. Play Result idle playing {:game_event, …}

  26. Play Result idle playing {:game_event, …}

  27. Architecture big-picture The Table-FSM Life of an in-game event Real

    world concerns BoldPoker <3 Elixir Lessons Learned & QA
  28. P1 P2 P3 P4 {Table,9} {Player,1}

  29. P1 P2 P3 P4 {Table,9} {Player,1} Ports

  30. P1 P2 P3 P4 {Table,9} {Player,1}

  31. P1 P2 P3 P4 {Table,9} {Player,1} {:game_event, :must_play_card}

  32. P1 {Table,9} {Player,1} {:game_event, …}

  33. P1 {Table,9} {Player,1} { "type": "mustPlayCard" } {:game_event, :must_play_card} ProtocolJSON.write/1

  34. P1 {Table,9} {Player,1}

  35. P1 {Table,9} {Player,1} { "type": "playACard", "card": "XH" } {:game_event,

    {:play_card,{:ten,:hearts}} ProtocolJSON.parse/1
  36. P1 {Table,9} {Player,1} {:game_event, …}

  37. Architecture big-picture The Table-FSM Life of an in-game event Real

    world concerns BoldPoker <3 Elixir Lessons Learned & QA
  38. P1 P2 P3 P4 {Table,9} {Player,1}

  39. haproxy active Erlang active Erlang standby Erlang standby

  40. haproxy active Erlang standby Erlang active Erlang standby

  41. :rpc.multicall(GameServer, switch_active, [node()])

  42. P1 P2 P3 P4 {Table,9} P5 P6 P7 old

  43. P1 P2 P3 P4 {Table,9} P5 P6 P7 old

  44. P1 P2 P3 P4 {Table,9} old

  45. P1 P2 P3 P4 {Table,9} old new {TableMigrator,9}

  46. P1 P2 P3 P4 {Table,9} old new {TableMigrator,9}

  47. P1 P2 P3 P4 {Table,9} P5 P6 P7 old new

    {TableMigrator,9}
  48. P1 P2 P3 P4 {Table,9} P5 P6 P7 old new

    {TableMigrator,9}
  49. P1 P2 P3 P4 {Table,9} P5 P6 P7 old new

    {TableMigrator,9}
  50. P1 P2 P3 P4 {Table,9} P5 P6 P7 old new

    {TableMigrator,9}
  51. P1 P2 P3 P4 {Table,9} P5 P6 P7 old new

    ConnectionProxy {TableMigrator,9}
  52. P1 P2 P3 P4 {Table,9} P5 P6 P7 old new

    {TableMigrator,9}
  53. P1 P2 P3 P4 {Table,9} old new {TableMigrator,9}

  54. None
  55. DealCards Knocking DealCards Play Result idle playing

  56. P1 P2 P3 P4 old new {TableMigrator,9} portable_state

  57. P1 2 3 4 old new { portable_state portable_state %{table_id:

     9,      player_ids:  [1,2,3,4],      positions:  %{1  =>  3,                                2  =>  0,                                3  =>  1,                                4  =>  2},      modifications:  [:short_hand],      last_game_id:  123   }
  58. P1 P2 P3 P4 old new {TableMigrator,9}

  59. old new {TableMigrator,9}

  60. old new {TableMigrator,9}

  61. None
  62. None
  63. old new {TableMigrator,9}

  64. old new {TableMigrator,9} P1

  65. old new {TableMigrator,9} P2 P3 P4 P1

  66. old new {TableMigrator,9} P2 P3 P4 P1 portable_state

  67. old new P2 P3 P4 P1 {Table,9}

  68. None
  69. P2 P3 P4 P1 {Table,9}

  70. Architecture big-picture The Table-FSM Life of an in-game event Real

    world concerns BoldPoker <3 Elixir Lessons Learned & QA
  71. All-in! Elixir Rewrite

  72. Why? Extensibility goals stdlib mix ExUnit

  73. :gen_fsm :ets GenServer Supervisor alias :ets, as: ETS

  74. proper Tests send_events(ClientDeviceIDs, ClientIP) -> ?FORALL(Event, event(), ?FORALL(Data, event_data(Event), ...

  75. parse_transform ?

  76. Architecture big-picture The Table-FSM Life of an in-game event Moving

    tables across servers BoldPoker <3 Elixir Lessons Learned & QA
  77. Perform en/decoding at the system border P1 { "type": "playACard",

    "card": "XH" } {:game_event, {:play_card,{:ten,:hearts}} ProtocolJSON.parse/1 WS
  78. Create your own behaviours for fun better reusability and profit

    easier testing {:next_stage, new_stage_data, new_state_data} {:end_game, new_stage_data, new_state_data}
  79. Distributed Erlang makes hard things easy ConnectionProxy

  80. DB failure? We got all data already, so finish the

    game, then just store result on disk
  81. Thank you! @msch featurebranch.com