Presentation #17
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
elixir 1.14.3-otp-25
|
BIN
presentation/architecture.png
Normal file
BIN
presentation/architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 520 KiB |
140
presentation/chessh.org
Normal file
140
presentation/chessh.org
Normal file
@ -0,0 +1,140 @@
|
||||
#+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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user