Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Designing a Real Time Game Engine in Erlang

Designing a Real Time Game Engine in Erlang

Talk delivered at Midwest.io

Jade Allen

July 15, 2014
Tweet

More Decks by Jade Allen

Other Decks in Technology

Transcript

  1. What's so great about it? •  Great industrial grade concurrency

    model •  Network transparency •  Good at slicing and dicing network protocols •  "For free" SMP scaling (up to about 8 cores) •  Fault tolerance mechanisms help avoid "defensive coding" •  Replace/upgrade running code without downtime •  Makes hard things easy
  2. What sucks about it? •  String handling is stupid. • 

    Syntax is off-putting. •  Library support isn't that great. •  A bit of a learning curve. •  Sometimes makes easy things hard.
  3. Erlang types •  Integers (example: 42) •  Floats (example: 3.14159)

    •  Binary data - TCP packet or JPG file (example: <<205, 72, 29, 1, 0, 92>>) •  Binary strings (example: <<"Hello world!">>) •  Tuples (example: {key, <<"Value">>}) •  Lists (example: [21, <<"foobar">>, 98.6]) •  Atoms (examples: 'error', 'Foo', 'POST') •  Functions (example: fun timer:sleep(2000) end)
  4. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n"); {0, _} -> io:format("fizz~n"); {_, 0} -> io:format("buzz~n"); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  5. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n", []); {0, _} -> io:format("fizz~n", []); {_, 0} -> io:format("buzz~n", []); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  6. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n", []); {0, _} -> io:format("fizz~n", []); {_, 0} -> io:format("buzz~n", []); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  7. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n", []); {0, _} -> io:format("fizz~n", []); {_, 0} -> io:format("buzz~n", []); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  8. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n", []); {0, _} -> io:format("fizz~n", []); {_, 0} -> io:format("buzz~n", []); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  9. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n", []); {0, _} -> io:format("fizz~n", []); {_, 0} -> io:format("buzz~n", []); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  10. -module(fizzbuzz). -export([t/0]). t() -> fizzbuzz(lists:seq(1, 100)). fizzbuzz([H | T]) ->

    case {H rem 3, H rem 5} of {0, 0} -> io:format("fizzbuzz~n", []); {0, _} -> io:format("fizz~n", []); {_, 0} -> io:format("buzz~n", []); {_, _} -> io:format("~p~n", [H]) end, fizzbuzz(T).
  11. 1.  What kind of game to design? 2.  Decomposing the

    game entities into processes. 3.  Deciding what state each process should manage.
  12. Port processes manage: •  A list of traders currently there

    (arrive, depart) •  A dictionary of goods (buy, sell) •  The quantity and current price of goods for sale
  13. Player processes manage: •  Current location •  Current amount of

    cash •  Current inventory of goods •  Current cargo capacity
  14. Game events are interactions between ports and players. •  Model

    ports and players as generic servers (gen_servers) •  Human -> Player API -> player gen_server -> port gen_server
  15. % API (to be called from the outside) % makes

    message format and data opaque move(Who, Where) -> case whereis(Where) of undefined -> error_logger:error_msg("There is no port named ~p", [Where]); _Pid -> gen_server:call(Who, {move, Where}) end. Example: move(Who, Where)
  16. % elsewhere in the gen_server implementation handle_call({move, Where}, _From, State

    = #state{ port = undefined }) -> parque_port:arrive(Where, self()), {reply, ok, State#state{ port = Where }}; handle_call({move, Where}, _From, State = #state{ port = Port }) -> parque_port:leave(Port, self()), parque_port:arrive(Where, self()), {reply, ok, State#state{ port = Where }};
  17. Wire up rest of player actions: •  buy(Who, What, Qty)

    •  sell(Who, What, Qty) •  Might be sensing a pattern...
  18. Wire up port API/message handlers: •  buy(Where, Who, What, Qty)

    •  sell(Where, Who, What, Qty) •  get_price(Where, What, Qty) •  list(Where) •  state(Where)
  19. Control flow example parque_player:buy('Jane', <<"fish">>, 10). 1.  'Jane' process exists?

    2.  Send 'Jane' process a message to buy fish for 10. 3.  'Jane' is in a port? 4.  'Jane' has sufficient capacity? 5.  'Jane' can afford price * qty? 6.  Send port process buy message (updates port state if request successful; port also notifies other player processes of buy activity) 7.  If successful reply from port process, update 'Jane' state
  20. Erlang is a good fit for highly reactive problems that

    require concurrency and (soft) real-time performance. Erlang is not (too) scary. Try it; you might like it. Writing games as a side project is very fun.