Date post: | 27-Jul-2015 |
Category: |
Engineering |
Upload: | zucaritask |
View: | 214 times |
Download: | 3 times |
At Wooga (mobile games):
Long running “player” sessions
Stateless backends are not enough
State is user centered: Good fit for JSON
At Wooga (mobile games):
Long running “player” sessions
Stateless backends are not enough
State is user centered: Good fit for JSON
Building Games
At Wooga (mobile games):
Long running “player” sessions
Stateless backends are not enough
State is user centered: Good fit for JSON
Building Games / Backends
At Wooga (mobile games):
Long running “player” sessions
Stateless backends are not enough
State is user centered: Good fit for JSON
Building Games / Backends
player sessions as events
At Wooga (mobile games):
Long running “player” sessions
Stateless backends are not enough
State is user centered: Good fit for JSON
Building Games / Backends
player sessions as events
stateless is not enough
WHAT ACTORS REALLY ARE
LIGHTWEIGHT CONCURRENT PROCESSES COMMUNICATING THROUGH MESSAGE PASSING THAT ENCAPSULATE STATE
WHAT ACTORS REALLY ARE
LIGHTWEIGHT CONCURRENT PROCESSES COMMUNICATING THROUGH MESSAGE PASSING THAT ENCAPSULATE STATE *bye BYE Threads, LockS, LATCHES..
WHAT ACTORS REALLY ARE
LIGHTWEIGHT CONCURRENT PROCESSES COMMUNICATING THROUGH MESSAGE PASSING THAT ENCAPSULATE STATE *bye BYE Threads, LockS, LATCHES..
PLEASE WELCOME:
Supervisors
Let it crash
Async calls
At most once messages...etc
HTTP KEY-VALUE STORE
(USER w/STATE)
HTTP
AND GET THE FEELING OF:
JSON PARAMS/RESPONSES
THE PROTOTYPE
HTTP KEY-VALUE STORE
(USER w/STATE)
HTTP
AND GET THE FEELING OF:
JSON PARAMS/RESPONSES PERSISTING STATE (AWS S3)
THE PROTOTYPE
HTTP KEY-VALUE STORE
(USER w/STATE)
HTTP
AND GET THE FEELING OF:
JSON PARAMS/RESPONSES PERSISTING STATE (AWS S3)
WRITING TESTS
THE PROTOTYPE
class User include Celluloid
TIMEOUT = 15
def initialize(id) @id = id after(TIMEOUT) { terminate } end
def state=(state) @state = state end
def state @state endend
class User include Celluloid
TIMEOUT = 15
def initialize(id) @id = id after(TIMEOUT) { terminate } end
def state=(state) @state = state end
def state @state endend
class User include Celluloid
TIMEOUT = 15
def initialize(id) @id = id after(TIMEOUT) { terminate } end
def state=(state) @state = state end
def state @state endend
require 'celluloid/autostart'
class Registry class << self def create(id) supervisor = User.supervise_as(id.to_sym, id) supervisor.actors.first end
def lookup(id) user = Celluloid::Actor[id.to_sym] user ||= create(id) end
endend
require 'celluloid/autostart'
class Registry class << self def create(id) supervisor = User.supervise_as(id.to_sym, id) supervisor.actors.first end
def lookup(id) user = Celluloid::Actor[id.to_sym] user ||= create(id) end
endend
global actor registry
E, [2015-03-11T20:53:14.981000 #11076] ERROR -- : Actor crashed!ThreadError: unable to create new native thread
org/jruby/RubyThread.java:440:in `initialize'org/jruby/RubyThread.java:389:in `new'
-module(wes_user).-behaviour(wes_actor).-export([init/1, command/3, command/4, key/1, to_struct/2, from_struct/2]).
-record(user_state, {id, data}).
init([Id]) -> { ok, #user_state{ id = Id, data = [ ] } }.
command(_, get, {Id}, #user_state{id = Id} = State) -> {reply, State#user_state.data, State};
key(Id) -> <<"id", Id/binary>>.
to_struct(_Actorname, #user_state{id = Id, data = Data}) -> EncodedData = jiffy:encode({Data}), jiffy:encode({[{id, Id}, {data, EncodedData}]}).
from_struct(_Key, Value) -> {Props} = jiffy:decode(Value), {_, Id} = lists:keyfind(<<"id">>, 1, Props), {_, EncodedData} = lists:keyfind(<<"data">>, 1, Props),
{Data} = jiffy:decode(EncodedData),
-module(wes_user).-behaviour(wes_actor).-export([init/1, command/3, command/4, key/1, to_struct/2, from_struct/2]).
-record(user_state, {id, data}).
init([Id]) -> { ok, #user_state{ id = Id, data = [ ] } }.
command(_, get, {Id}, #user_state{id = Id} = State) -> {reply, State#user_state.data, State};
key(Id) -> <<"id", Id/binary>>.
to_struct(_Actorname, #user_state{id = Id, data = Data}) -> EncodedData = jiffy:encode({Data}), jiffy:encode({[{id, Id}, {data, EncodedData}]}).
from_struct(_Key, Value) -> {Props} = jiffy:decode(Value), {_, Id} = lists:keyfind(<<"id">>, 1, Props), {_, EncodedData} = lists:keyfind(<<"data">>, 1, Props),
{Data} = jiffy:decode(EncodedData),
-module(wes_user).-behaviour(wes_actor).-export([init/1, command/3, command/4, key/1, to_struct/2, from_struct/2]).
-record(user_state, {id, data}).
init([Id]) -> { ok, #user_state{ id = Id, data = [ ] } }.
command(_, get, {Id}, #user_state{id = Id} = State) -> {reply, State#user_state.data, State};
key(Id) -> <<"id", Id/binary>>.
to_struct(_Actorname, #user_state{id = Id, data = Data}) -> EncodedData = jiffy:encode({Data}), jiffy:encode({[{id, Id}, {data, EncodedData}]}).
from_struct(_Key, Value) -> {Props} = jiffy:decode(Value), {_, Id} = lists:keyfind(<<"id">>, 1, Props), {_, EncodedData} = lists:keyfind(<<"data">>, 1, Props),
{Data} = jiffy:decode(EncodedData),
defmodule User do
def start_link do Agent.start_link(fn -> HashDict.new end) end
def get(user, key) do Agent.get(user, &HashDict.get(&1, key)) end
def put(user, key, value) do Agent.update(user, &HashDict.put(&1, key, value)) end
def delete(bucket, key) do Agent.get_and_update(bucket, &HashDict.pop(&1, key)) endend
defmodule User do
def start_link do Agent.start_link(fn -> HashDict.new end) end
def get(user, key) do Agent.get(user, &HashDict.get(&1, key)) end
def put(user, key, value) do Agent.update(user, &HashDict.put(&1, key, value)) end
def delete(bucket, key) do Agent.get_and_update(bucket, &HashDict.pop(&1, key)) endend
Agents
defmodule User do @behaviour :wes_actor
def init([id]) do {:ok, %{id: id, data: %{} }} end
def command(_session, :update, {user_id, {key, value}}, state) do new_state = %{ state | :data => Dict.put(state.data, key, value)} {:ok, new_state} end
def command(_session, :get, user_id, state) do {:reply, state.data, state} end
def key(id) do "#{id}" end
def to_struct(_actor_name, state) do {:ok, state} = Jazz.encode(state) state end
def from_struct(_key, value) do
Wes with
defmodule User do @behaviour :wes_actor
def init([id]) do {:ok, %{id: id, data: %{} }} end
def command(_session, :update, {user_id, {key, value}}, state) do new_state = %{ state | :data => Dict.put(state.data, key, value)} {:ok, new_state} end
def command(_session, :get, user_id, state) do {:reply, state.data, state} end
def key(id) do "#{id}" end
def to_struct(_actor_name, state) do {:ok, state} = Jazz.encode(state) state end
def from_struct(_key, value) do
Wes with
case class StateUpdate(state: Map[String, String])case class StateGet() class User(userId:String) extends Actor { var userState:Map[String, String] = Map() def receive = { case StateUpdate(newState) => { newState.foreach { case (key, value) => userState += (key -> value) } } case StateGet() => { sender ! userState } case ReceiveTimeout => { val storage = context.actorSelection("/user/storage") storage ! SaveUserState(userId, userState) self ! akka.actor.PoisonPill } }}
case class StateUpdate(state: Map[String, String])case class StateGet() class User(userId:String) extends Actor { var userState:Map[String, String] = Map() def receive = { case StateUpdate(newState) => { newState.foreach { case (key, value) => userState += (key -> value) } } case StateGet() => { sender ! userState } case ReceiveTimeout => { val storage = context.actorSelection("/user/storage") storage ! SaveUserState(userId, userState) self ! akka.actor.PoisonPill } }}
"A UserActor" should "be able to set a new value in its state" in { val user = TestActorRef(Props[User]) val update = Map("foo"-> "bar") user ! StateUpdate(update) user.underlyingActor.asInstanceOf[User] userState should be(update) }
A TEST:
"A UserActor" should "be able to set a new value in its state" in { val user = TestActorRef(Props[User]) val update = Map("foo"-> "bar") user ! StateUpdate(update) user.underlyingActor.asInstanceOf[User] userState should be(update) }
A TEST: