Akka NET veblushgmail githubveblush actor receiver User Actor
Akka. NET 으로 만드는 온라인 게임 서 버 김이선 veblush@gmail | github/veblush
왜 액터 모델? // actor. receiver User. Actor. On. Chat(Chat m) { _count += 1; Print(m. Message); } // sender Get. User. Actor(). Send( new Chat { Message = "Hello? " }); User. Actor _count M 3 M 2 M 1 On. Chat
Hello. Actor class Hello. Actor : Receive. Actor { int _count; public Hello. Actor() { Receive<Hello>(m => { _count += 1; Console. Write. Line($"Hello {m. Who}")}); } _count M 3 M 2 M 1 On. Hello
greeter Actor. Ref // create actor IActor. Ref greeter = system. Actor. Of<Hello. Actor>("greeter"); _count M 3 M 2 M 1 On. Hello // send message greeter. Tell(new Hello("World")); Hello(“World”) greeter
Actor 계층 구조 class Worker : Untyped. Actor { IActor. Ref counter. Service = Context. Actor. Of<Counter. Service>("counter"); override Supervisor. Strategy() { return new One. For. One. Strategy(ex => { if (ex is Service. Unavailable. Exception) return Directive. Stop; return Directive. Escalate; }); }
원격 Node. A 원격 Actor 에 메시지를 보낼 수 있음 - Actor. Ref 에 대상 주소 정보 - 위치 투명성 M 3 Actor State M 2 M 1 Behaviour 원격 Actor 를 만들 수 있음 Actor. Ref Node. A: Actor Node. B
원격 // 서버 using (var system = Actor. System. Create("My. Server", config)) { system. Actor. Of<Hello. Actor>("greeter"); . . . } // 클라이언트 using (var system = Actor. System. Create("My. Client", config)) { IActor. Ref greeter = system. Actor. Selection( "akka. tcp: //My. Server@localhost: 8080/user/greeter"); greeter. Tell(new Hello("World")); }
클러스터 class Simple. Cluster : Untyped. Actor { Cluster = Cluster. Get(System); override void On. Receive(object message) { var up = message as Cluster. Event. Member. Up; if (up != null) { Print("Up: {0}", up. Member); } } Node. A B C Node. B A Node. C A B C
Discovery / Table Node. A Node. B Interface Client Actor State Sync ? DB
Akka. Interfaced https: //github. com/Salad. Lab/Akka. Interfaced Node. A Node. B Interface Actor
Akka. Interfaced IHello M 3 M 2 greeter _count M 1 M 3 M 2 M 1 On. Hello IHello(…) greeter IHello(…) greeter
Akka. NET 스타일 // define message class Hello { public string Name; } class Hello. Result { public string Say; } // implement actor class Hello. Actor : Receive. Actor { public Hello. Actor() { Receive<Hello>(m => { Sender. Tell(new Hello. Result($"Hello {m. Name}!")}); // use actor var result = await actor. Ref. Ask<Hello. Result>(new Hello("World")); Print(result. Say);
Akka. Interfaced 스타일 // define interface IHello : IInterfaced. Actor { Task<string> Say. Hello(string name); } // implement actor class Hello. Actor : Interfaced. Actor<Hello. Actor>, IHello { async Task<string> IHello. Say. Hello(string name) { return $"Hello {name}!"; } // use actor Print(await hello. Ref. Say. Hello("World"));
Akka. Interfaced. Slim. Socket https: //github. com/Salad. Lab/Akka. Interfaced. Slim. Socket Node. A Node. B Interface Client Actor
Akka. Interfaced. Slim. Socket Client Server Actor 1 Ref Actor 2 Client. Session Slim. Socket. Client Slim. Socket. Server protobuf/tcp
Akka. Cluster. Utility https: //github. com/Salad. Lab/Akka. Cluster. Utility Discovery / Table Node. A Node. B Actor ?
Akka. Cluster. Utility: Actor. Discovery Actor Discovery - 관심 있는 Actor 를 찾아내기 위해
Akka. Cluster. Utility: Actor. Discovery Node. A Node. B Service. Actor User. Actor Listen Notify Register User. Actor Listen Notify Actor. Discovery share state
Akka. Cluster. Utility: Actor. Discovery // register actor class Service. Actor { override void Pre. Start(){ Discovery. Tell(new Register(Self, nameof(Service. Actor))); } // discover registered actor class User. Actor { override void Pre. Start() { Discovery. Tell(new Monitor(nameof(Service. Actor))); } void On. Actor. Up(Actor. Up m) {. . . } void On. Actor. Down(Actor. Down m) {. . . }
Akka. Cluster. Utility: Distributed. Actor. Tabl Distributed Actor Table - 여러 클러스터 노드에 분산되는 액터 테이블 - Sharding 과 유사하나 좀 더 단순한 구현체 - 마스터 테이블 존재 (SPOF/B)
Akka. Cluster. Utility: Distributed. Actor. Tabl Node. A Node. B Actor. Table ID 1 2 3 Actor. Ref Actor 1 Actor 2 Actor 3 Node. C Actor 1 Actor 3 Actor 2 Actor 4
Akka. Cluster. Utility: Distributed. Actor. Tabl // create table var table = System. Actor. Of( Props. Create(() => new Table("Test", . . . ))); // create actor on table var reply = await table. Ask<Create. Reply>(new Create(id, . . . )); reply. Actor; // get actor from table var reply = await table. Ask<Get. Reply>(new Get(id)); reply. Actor;
Trackable. Data https: //github. com/Salad. Lab/Trackable. Data Node. A Client Node. B Actor State Sync DB
Trackable. Data Client User Gold=10 Cash=20 User Gold=15 Cash=20 DB Server Snapshot User Gold=10 Cash=20 Create Change Gold+=5 Save User Gold=15 Cash=20 User Gold=10 Cash=20 User Gold=15 Cash=20
Trackable. Data interface IUser. Data : ITrackable. Poco<IUser. Data> { string Name { get; set; } int Level { get; set; } int Gold { get; set; } } var u = new Trackable. User. Data(); // make changes u. Name = "Bob"; u. Level = 1; u. Gold = 10; Print(u. Tracker); // { Name: ->Bob, Level: 0 ->1, Gold: 0 ->10 }
https: //github. com/Salad. Lab/Tic. Tac. Toe
프로젝트 구성 Domain. Tests 450 cloc Game. Server 965 cloc Game. Server. Tests Game. Client 1141 cloc
클러스터 노드 구성 Master User Game. Pair Maker User Game User Table User Login Game. Bot Game Table
액터 구성 Client Session User Login Mongo. DB User Game. Pair Maker Game. Bot
로그인: 액터 Login User Login Create Client Session Bind User Mongo. DB
로그인: 코드 // client requests login to server var t 1 = login. Login(id, password, observer. Id); yield return t 1. Wait. Handle; // check account, create user actor and bind it to client async Task<Login. Result> IUser. Login(id, password) { var user. Id = Check. Account(id, password); var user = Create. User. Actor(user. Id); await User. Table. Add. User. Async(user. Id, user); var bind. Id = await Client. Session. Bind. Async(user, typeof(IUser)); // client gets user actor var user = new User. Ref(t 1. Result);
매치메이킹: 액터 Client Session User Register Game. Pair Maker Created Create Game
매치메이킹: 코드 // client requests pairing from User. Actor yield return G. User. Register. Pairing(observer. Id). Wait. Handle; // User. Actor forwards a request to Game. Pair. Maker. Actor class Game. Pair. Maker. Actor : . . . { void Register. Pairing(user. Id, user. Name, . . . ) { Add. To. Queue(user. Id); } void On. Schedule() { if (Queue. Count >= 2) { user 0, user 1 = Queue. Pop(2); Create. New. Game(); user 0. Send. Notice. To. User(game. Id); user 1. Send. Notice. To. User(game. Id);
게임 입장: 액터 Client Session Join Bind User Join Game
게임 입장: 코드 // client sends a join request to User. Actor. var ret = G. User. Join. Game(room. Id, observer. Id); yield return ret. Wait. Handle; // User. Actor forwards a request to Game. Actor. // when done, grant Game. Actor to Client as IGame. Player. class User. Actor : . . . { async Task<. . . > IUser. Join. Game(long game. Id, int observer. Id) { var game = await Game. Table. Get. Async(game. Id); await game. Join(. . . ); var bind. Id = await Bind. Async(game. Actor, typeof(IGame. Player)); return. . . ; }
게임 진행: 턴 명령: 액터 Client Session Game. Ref. Make. Move(2, 1) Make. Move Game
게임 진행: 턴 명령: 코드 // client sends a move command to Game. Actor class Game. Scene : Mono. Behaviour, IGame. Observer { void On. Board. Grid. Clicked(int x, int y) { _my. Player. Make. Move(new Place. Position(x, y)); } // Game. Actor proceeds the game by command public class Game. Actor : . . . { void Make. Move(Place. Position pos, long player. User. Id) { Do. Game. Logic(); . . . }
게임 진행: 진행 이벤트: 액터 Make. Move Game Client Session Make. Move Game. Observer. Make. Move(2, 1)
게임 진행: 진행 이벤트: 코드 // Game. Actor broadcasts game events to clients public class Game. Actor : . . . { void Make. Move(Place. Position pos, long player. User. Id) {. . . Notify. To. All. Observers((id, o) => o. Make. Move(. . . )); } // client consumes game events class Game. Scene : Mono. Behaviour, IGame. Observer { void IGame. Observer. Make. Move(. . . ) { Board. Set. Mark(. . . ); }
게임 종료: 액터 Mongo. DB Client Session Update User End Game Kill
게임 종료: 코드 // Update. Actor updates user state when the game is over void IGame. User. Observer. End(long game. Id, Game. Result result) { _user. Context. Data. Game. Count += 1; // flush changes to client and storage _user. Event. Observer. User. Context. Change(_user. Context. Tracker); Mongo. Db. Mapper. Save. Async(_user. Context. Tracker, _id); _user. Context. Tracker = new Trackable. User. Context. Tracker(); } // when no one left in Game. Actor, kill actor void Leave(long user. Id) { Notify. To. All. Observers((id, o) => o. Leave(player. Id)); if (_players. Count() == 0) { Self. Tell(Interfaced. Poison. Pill. Instance);
- Slides: 78