chessh/presentation/chessh.org

141 lines
4.2 KiB
Org Mode
Raw Normal View History

2023-02-22 19:06:25 -05:00
#+TITLE: Practicing Elixir by building concurrent, distributed, multiplayer games in the terminal
#+AUTHOR: Lizzy Hunt (Simponic)
* Introduction
This meeting should be being streamed live, at [[https://linux.usu.edu/streams]].
#+BEGIN_SRC elixir
defmodule Hello do
def hello() do
"Hello, Linux Club!"
|> IO.puts
end
end
Hello.hello()
#+END_SRC
** CheSSH
CheSSH is a multiplayer distributed game of chess over SSH - let's take a quick look before diving into Elixir!
[[https://chessh.linux.usu.edu]]
* Elixir - Functional Meta-Programming
Elixir is a self-proclaimed "dynamic, functional language for building scalable and maintainable applications".
Obviously, one of Elixir's main selling points must be its functional paradigm - its the second in the list.
We'll take a quick look at some features of Elixir, and find that functional programming brings a lot to the table.
* Elixir - Concurrency
Elixir is built on top of (and completely interoperable with) Erlang - a language developed to build massively fault-tolerant systems in the 80's
for large telephone exchanges with hundreds of thousands of users.
You can imagine (if you look past the many problems with this statement), Elixir and Erlang to be analogous to Python and C,
respectively - but without the massive performance penalty.
** The BEAM
The BEAM powers Elixir's concurrency magic; by running a VM executing Erlang bytecode that holds one OS thread per core,
and a separate process scheduler (and queue) on each.
Imagine an army of little goblins, and you give each a todo list. The goblins then go complete the tasks in the order best
suited for them, and have the added benefit that they can talk to each other.
** Concurrency - Demo!
Here we will open up two terminals: one running an Elixir REPL on my machine, and another to SSH into my android here
#+BEGIN_SRC python
import subprocess
import string
import random
cookie = ''.join(random.choices(string.ascii_uppercase +
string.digits, k=32))
host = "host"
android = "a02364151-23.bluezone.usu.edu"
h = subprocess.Popen(f"alacritty -e rlwrap --always-readline iex --name lizzy@{host} --cookie {cookie}".split())
a = subprocess.Popen(f"alacritty -e ssh u0_a308@{android} -p 2222 rlwrap --always-readline iex --name android@{android} --cookie {cookie}".split())
#+END_SRC
#+BEGIN_SRC elixir
defmodule SpeakServer do
@sleep_between_msg 2000
def loop(queue \\ []) do
case queue do
[head | tail] ->
speak(head)
:timer.sleep(@sleep_between_msg)
loop(tail)
[] ->
receive do
msg ->
loop(queue ++ [msg])
end
end
end
defp speak(msg) do
System.cmd("espeak", [msg])
end
end
defmodule KVServer do
require Logger
@max_len_msg 32
def start(speak_server_pid, port) do
{:ok, socket} =
:gen_tcp.listen(port, [:binary, packet: :line, active: false, reuseaddr: true])
loop_acceptor(socket, speak_server_pid)
end
defp loop_acceptor(socket, speak_server_pid) do
{:ok, client} = :gen_tcp.accept(socket)
Task.start_link(fn -> serve(client, speak_server_pid) end)
loop_acceptor(socket, speak_server_pid)
end
defp serve(socket, speak_server_pid) do
msg = socket
|> read_line()
|> String.trim()
if valid_msg(msg) do
send(speak_server_pid, msg)
end
serve(socket, speak_server_pid)
end
defp read_line(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
data
end
defp valid_msg(msg), do: String.length(msg) < @max_len_msg && String.match?(msg, ~r/^[A-Za-z ]+$/)
end
android = :"android@a02364151-23.bluezone.usu.edu"
Node.connect(android)
speak_server_pid = Node.spawn(android, &SpeakServer.loop/0)
KVServer.start(speak_server_pid, 42069)
#+END_SRC
This demo shows how we can:
+ Connect nodes running Elixir
+ Spawn processes on nodes and inter process communication
+ Basic Elixir constructs (pattern matching, atoms, function calls, referencing functions)
* CheSSH
With a very brief and quick exploration into concurrency with Elixir, we can now explore the architecture of CheSSH,
and how it came to be on 5 raspberry pis
<picture_of_pis>