Skip to content

Commit

Permalink
Clean that up a bit.
Browse files Browse the repository at this point in the history
  • Loading branch information
ConnorRigby committed Aug 2, 2018
1 parent bccc4a8 commit c599ea2
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 51 deletions.
1 change: 1 addition & 0 deletions lib/circular_list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ defmodule CircularList do
def update_current(this, fun) do
index = get_index(this)
current_value = at(this, index)

if current_value do
result = fun.(current_value)
%{this | items: Map.put(this.items, index, result)}
Expand Down
114 changes: 66 additions & 48 deletions lib/csvm.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
defmodule Csvm do
@moduledoc """
Manages many FarmProcs
"""

use GenServer
use Bitwise, only: [bsl: 2]
alias Csvm.{AST, FarmProc, ProcStorage}
import Csvm.Utils
alias AST.Heap
require Logger

# Frequency of vm ticks.
@tick_timeout 20
@kinds_that_need_fw [:wait]
@kinds_aloud_while_locked []
Expand All @@ -20,6 +26,7 @@ defmodule Csvm do

@opaque job_id :: CircularList.index()

@doc "Execute an rpc_request, this is sync."
def rpc_request(pid \\ __MODULE__, %{} = map, fun)
when is_function(fun) do
%AST{} = ast = AST.decode(map)
Expand All @@ -40,8 +47,8 @@ defmodule Csvm do
end
end

def sequence(pid \\ __MODULE__, %{} = map, id, fun)
when is_function(fun) do
@doc "Execute a sequence. This is async."
def sequence(pid \\ __MODULE__, %{} = map, id, fun) when is_function(fun) do
job = queue(pid, map, id)

spawn(fn ->
Expand All @@ -57,6 +64,10 @@ defmodule Csvm do
end)
end

# Queues some data for execution.
# If kind == :emergency_lock or :emergency_unlock
# (or this is an rpc request with the first item being one of those.)
# this ast will immediately execute the `hyper_io_layer` function.
@spec queue(GenServer.server(), map, integer) :: job_id
defp queue(pid, %{} = map, page_id) when is_integer(page_id) do
case AST.decode(map) do
Expand All @@ -75,17 +86,21 @@ defmodule Csvm do
%AST{} = ast ->
%Heap{} = heap = AST.slice(ast)
%Address{} = page = addr(page_id)

case GenServer.call(pid, {:queue, heap, page}) do
{:error, :busy} -> queue(pid, map, page_id)
job -> job
end
end
end

# Polls the GenServer until it returns a FarmProc with a stopped status
@spec await(GenServer.server(), job_id) :: FarmProc.t()
defp await(pid, job_id) do
case GenServer.call(pid, {:lookup, job_id}) do
{:error, :busy} -> await(pid, job_id)
{:error, :busy} ->
await(pid, job_id)

%FarmProc{} = proc ->
case FarmProc.get_status(proc) do
status when status in [:ok, :waiting] ->
Expand All @@ -97,7 +112,7 @@ defmodule Csvm do
end

_ ->
raise("no job by that identifier")
raise(ArgumentError, "no job by that identifier")
end
end

Expand All @@ -119,12 +134,14 @@ defmodule Csvm do
timer = start_tick(self())
storage = ProcStorage.new(Keyword.fetch!(args, :name))
io_fun = Keyword.fetch!(args, :process_io_layer)
hyper_fun = Keyword.fetch!(args, :hyper_io_layer)
unless is_function(io_fun), do: raise(ArgumentError)
unless is_function(hyper_fun), do: raise(ArgumentError)

{:ok,
%Csvm{
process_io_layer: io_fun,
hyper_io_layer: Keyword.fetch!(args, :hyper_io_layer),
hyper_io_layer: hyper_fun,
tick_timer: timer,
proc_storage: storage
}}
Expand All @@ -137,35 +154,34 @@ defmodule Csvm do

def handle_call(:emergency_unlock, _from, %Csvm{} = state) do
apply_callback(state.hyper_io_layer, [:emergency_unlock])
{:reply, :ok, %{state | hyper_state: :emergency_unlock}}
{:reply, :ok, %{state | hyper_state: nil}}
end

def handle_call(_, _from, {:busy, state}) do
{:reply, {:error, :busy}, {:busy, state}}
end

def handle_call(
{:queue, %Heap{} = heap, %Address{} = page},
_from,
%Csvm{} = state
) do
%FarmProc{} = new_proc = FarmProc.new(state.process_io_layer, page, heap)
def handle_call({:queue, %Heap{} = h, %Address{} = p}, _, %Csvm{} = state) do
%FarmProc{} = new_proc = FarmProc.new(state.process_io_layer, p, h)
index = ProcStorage.insert(state.proc_storage, new_proc)
{:reply, index, state}
end


def handle_call({:lookup, id}, _from, %Csvm{} = state) do
cleanup = fn(proc, state) ->
cleanup = fn proc, state ->
ProcStorage.delete(state.proc_storage, id)

if proc.ref == state.fw_proc do
IO.puts "cleaning fw_proc: #{inspect proc.ref}"
{:reply, proc, %{state | fw_proc: nil}}
else
{:reply, proc, state}
end
end

# Looks up a FarmProc, causes a few different side affects.
# if the status is :done or :crashed, delete it from ProcStorage.
# if deleted, and this proc owns the firmware,
# delete it from there also.
case ProcStorage.lookup(state.proc_storage, id) do
%FarmProc{status: :crashed} = proc ->
cleanup.(proc, state)
Expand All @@ -178,62 +194,68 @@ defmodule Csvm do
end
end

def handle_info(:tock, %Csvm{} = state) do
# IO.puts "[info] enter"
def handle_info(:tick, %Csvm{} = state) do
pid = self()
# Calls `do_tick/3` with either
# * a FarmProc that needs updating
# * a :noop atom
# state is set to {:busy, old_state}
# until `do_step` calls
# send(pid, %Csvm{})
ProcStorage.update(state.proc_storage, &do_step(&1, pid, state))
{:noreply, {:busy, state}}
end

# make sure to update the timer _AFTER_ we tick.
# This message comes from the do_step/3 function that gets called
# When updating a FarmProc.
def handle_info(%Csvm{} = state, {:busy, _old}) do
# IO.puts "[info] exit"
# make sure to update the timer _AFTER_ we tick.
new_timer = start_tick(self())
{:noreply, %Csvm{state | tick_timer: new_timer}}
end

defp start_tick(pid, timeout \\ @tick_timeout),
do: Process.send_after(pid, :tock, timeout)

def do_step(:noop, pid, state) do
send(pid, state)
end
do: Process.send_after(pid, :tick, timeout)

@doc false
# If there are no procs
def do_step(:noop, pid, state), do: send(pid, state)

# If the proc is crashed or done, don't step.
def do_step(%FarmProc{status: :crashed} = farm_proc, pid, state) do
IO.puts "crash tick."
IO.puts("crash tick.")
send(pid, state)
farm_proc
end

def do_step(%FarmProc{status: :done} = farm_proc, pid, state) do
IO.puts "done tick."
IO.puts("done tick.")
send(pid, state)
farm_proc
end

use Bitwise, only: [bsl: 2]

# If nothing currently owns the firmware,
# Check kind needs fw,
# Check kind is aloud while the bot is locked,
# Check if bot is unlocked
# If kind needs fw, update state.
def do_step(%FarmProc{} = farm_proc, pid, %{fw_proc: nil} = state) do
pc_ptr = FarmProc.get_pc_ptr(farm_proc)
kind = FarmProc.get_kind(farm_proc, pc_ptr)
b0 = (kind in @kinds_aloud_while_locked) |> bit()
b1 = (kind in @kinds_that_need_fw) |> bit()
b2 = (true) |> bit()
b2 = true |> bit()
b3 = (state.hyper_state == :emergency_lock) |> bit()
bits = bsl(b0, 3) + bsl(b1, 2) + bsl(b2, 1) + b3

if should_step(bits) do
if bool(b1) do
IO.puts "flagging fw_proc: #{inspect farm_proc.ref}"
send(pid, %{state | fw_proc: farm_proc.ref})
else
send(pid, state)
end
# Update state if this kind needs fw.
if bool(b1),
do: send(pid, %{state | fw_proc: farm_proc.ref}),
else: send(pid, state)

actual_step(farm_proc)
else
IO.inspect(bits, base: :binary, label: "not stepping")
IO.inspect(Integer.digits(bits, 2), label: "not stepping")
IO.inspect(bits, label: "not stepping")
send(pid, state)
farm_proc
end
Expand All @@ -247,16 +269,11 @@ defmodule Csvm do
b2 = (farm_proc.ref == state.fw_proc) |> bit()
b3 = (state.hyper_state == :emergency_lock) |> bit()
bits = bsl(b0, 3) + bsl(b1, 2) + bsl(b2, 1) + b3
if should_step(bits) do
send(pid, state)
actual_step(farm_proc)
else
IO.inspect(bits, base: :binary, label: "not stepping")
IO.inspect(Integer.digits(bits, 2), label: "not stepping")
IO.inspect(bits, label: "not stepping")
send(pid, state)
farm_proc
end
send(pid, state)

if should_step(bits),
do: actual_step(farm_proc),
else: farm_proc
end

defp should_step(0b0000), do: true
Expand All @@ -281,6 +298,7 @@ defmodule Csvm do
defp bool(1), do: true
defp bool(0), do: false

@spec actual_step(FarmProc.t()) :: FarmProc.t()
defp actual_step(farm_proc) do
try do
FarmProc.step(farm_proc)
Expand Down
7 changes: 4 additions & 3 deletions lib/csvm/proc_storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ defmodule Csvm.ProcStorage do

@spec insert(proc_storage, FarmProc.t()) :: index
def insert(this, %FarmProc{} = farm_proc) do
Agent.get_and_update(this, fn(cl) ->
new_cl = cl
Agent.get_and_update(this, fn cl ->
new_cl =
cl
|> CircularList.push(farm_proc)
|> CircularList.rotate()

{CircularList.get_index(new_cl), new_cl}
end)

end

@spec current_index(proc_storage) :: index
Expand Down

0 comments on commit c599ea2

Please sign in to comment.