51. Cookbook 1: Creating a room-based MO game¶
When making an MO game using iFun Engine, you can use the engine’s event subsystem to create game sessions played by multitudes of people in real time.
This document will explain these concepts by making a simple MO room:
Asynchronous event handling
Unlocked synchronization
Implementing asynchronous functions (using
C++
11, 14, 17)
51.1. Setting up a room project¶
Use the following commands to create a room
project:
$ funapi_initiator room
$ room-source/setup_build_environment --type=makefile
Modify the following files:
CMakeLists.txt
src/event_handlers.cc
src/object_model/example.json
Modify to use the latest C++
Change set(WANT_CXX11 false)
to true
in the CMakeLists.txt
file in the source tree root. Ignore if this is already
true
.
# 중략
# Needs C++1x features? (Requires modern C++ compiler)
set(WANT_CXX11 true)
$ funapi_initiator room --csharp
$ room-source/setup_build_environment --type=makefile
Modify the following files:
CMakeLists.txt
mono/server.cs
src/object_model/example.json
51.2. Implementing the MO session¶
51.2.1. Implementing the rooms¶
First, add a simple ORM.
src/object_model/example.json
{
"User": {
"Name": "String KEY",
"Level": "Integer",
"CharacterId": "Integer"
}
}
The following shows the implementation of a simple room.
Note
You can use a predefined ErrorCode when sending result messages, but success and failure have been handled simply here for the sake of giving a simple example.
src/event_handlers.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | #include <funapi.h>
void SendMessage(const Ptr<Session> &session, const string &msg_type,
const bool result) {
Json message;
message["result"] = result;
session->SendMessage(msg_type, message);
return;
}
// 하나의 Room 을 관리하기 위한 클래스입니다.
class Room : public boost::enable_shared_from_this<Room> {
public:
DECLARE_CLASS_PTR(Room);
typedef boost::unordered_map<Uuid, Ptr<Room> > RoomMap;
// 유저를 관리하기 위한 struct 입니다.
// 유저를 위한 데이터를 모두 DB 에 저장하지 않을 뿐더러,
// DB 에 저장되지 않는 데이터 중에 방 관련 데이터를 기록해야되기 때문에,
// 이 클래스는 ORM 의 User 와는 별개입니다.
struct User {
User(const Ptr<Session> &_session, const string &_name,
const int64_t _character_id, const int64_t _level)
: session(_session), name(_name),
character_id(_character_id), level(_level) {
}
// 유저의 Session 입니다.
const Ptr<Session> session;
// 유저의 이름입니다.
string name;
// 유저의 캐릭터 id 입니다.
int64_t character_id;
// 유저의 레벨입니다.
int64_t level;
};
static Ptr<Room> Create(const string &name,
const int64_t joinable_min_level,
const string &master_user_name) {
Uuid id = RandomGenerator::GenerateUuid();
Ptr<Room> room(new Room(id, name, joinable_min_level, master_user_name));
boost::mutex::scoped_lock lock(the_mutex);
the_rooms.insert(std::make_pair(id, room));
return room;
}
static Ptr<Room> Find(const Uuid &id) {
boost::mutex::scoped_lock lock(the_mutex);
RoomMap::iterator it = the_rooms.find(id);
if (it == the_rooms.end()) {
return Room::kNullPtr;
}
return it->second;
}
static Json GetRooms() {
Json rooms;
{
boost::mutex::scoped_lock lock(the_mutex);
BOOST_FOREACH(const RoomMap::value_type &pair, the_rooms) {
string room_id = boost::lexical_cast<string>(pair.first);
rooms[room_id] = pair.second->GetInfo();
}
}
return rooms;
}
~Room() {
sessions_.clear();
}
const Uuid &id() const { return id_; }
const string &name() const { return name_; }
int64_t joinable_min_level() const { return joinable_min_level_; }
const string &master_user_name() const { return master_user_name_; }
void SetMasterUserName(const string &master_user_name) {
master_user_name_ = master_user_name;
}
// 유저가 Room 에 입장하는 것을 처리합니다.
// 클라이언트로부터 "client_join" 이라는 메시지가 오면 호출됩니다.
void HandleJoin(const Ptr<Session> &session, const string &name,
const int64_t character_id, const int64_t level) {
// 유저가 Room 에 입장할 수 있는지 조건을 검사합니다.
// 이미 입장했었는지 확인합니다.
if (sessions_.count(session->id()) > 0) {
SendMessage(session, "client_join", false);
return;
}
// 이름이나 캐릭터 id 가 정상인지 확인합니다.
if (name.empty() || character_id < 0) {
SendMessage(session, "client_join", false);
return;
}
// 입장 가능한 최소 레벨을 만족하는지 검사합니다.
if (joinable_min_level() < level) {
SendMessage(session, "client_join", false);
return;
}
// 모든 조건에 만족하였습니다. 이제 Room 에 입장시키도록 하겠습니다.
Ptr<User> user(new User(session, name, character_id, level));
bool has_not_joined =
sessions_.insert(std::make_pair(session->id(), user)).second;
// 입장한 적이 없어야 합니다.
BOOST_ASSERT(has_not_joined);
// 성공적으로 입장했습니다.
// session context 에 room id 를 저장하여
// 메시지 핸들러에서 클라이언트가 room id 를 보내지 않아도
// session context 로부터 room id 를 가져와 사용하도록 하겠습니다.
session->AddToContext("room_id", boost::lexical_cast<string>(id()));
// 입장하는 유저를 포함한 Room 내 유저들에게
// 입장 메시지를 broadcasting 합니다.
Json message;
// 입장한 유저의 정보를 입력합니다.
message["name"] = name;
message["character_id"] = character_id;
message["level"] = level;
for (const auto &v: sessions_) {
// server_user_join 이라는 msg type 으로 메시지를 전송합니다.
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (v.second->session->IsTransportAttached()) {
v.second->session->SendMessage("server_user_join", message);
}
}
}
// Room 나가기를 처리합니다.
void HandleLeave(const Ptr<Session> &session) {
string user_name;
{
auto user = sessions_.find(session->id());
// Room 에 존재하지 않는 session(유저) 입니다.
if (user == sessions_.cend()) {
SendMessage(session, "client_leave", false);
return;
}
user_name = user->second->name;
// Room 에서 유저를 삭제합니다.
sessions_.erase(user);
// 모든 유저가 Room 에서 나갔으므로 방을 빈방으로 처리하겠습니다.
// 여기서는 간단하게 name 만 지우면 빈방이 되겠다고 하겠습니다.
if (sessions_.size() == 0) {
name_ = "";
}
// 떠나는 유저에게 메시지를 전송합니다.
SendMessage(session, "client_leave", true);
}
// 떠나는 유저를 제외한 Room 내 유저들에게 메시지를 전송합니다.
Json message;
message["name"] = user_name;
for (const auto &v: sessions_) {
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (v.second->session->IsTransportAttached()) {
v.second->session->SendMessage("server_user_leave", message);
}
}
}
// Room 내 유저들과 채팅하는 것을 처리합니다.
void HandleChat(const Ptr<Session> &session, const string &msg) {
string user_name;
{
auto user = sessions_.find(session->id());
// Room 에 존재하지 않는 session(유저) 입니다.
if (user == sessions_.cend()) {
SendMessage(session, "client_chat", false);
return;
}
user_name = user->second->name;
}
// 유저 이름이 비어 있으면 안됩니다.
BOOST_ASSERT(not user_name.empty());
// Room 내 유저들에게 채팅 메시지를 전송합니다.
Json message;
message["name"] = user_name;
message["msg"] = msg;
for (const auto &v: sessions_) {
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (v.second->session->IsTransportAttached()) {
v.second->session->SendMessage("server_user_chat", message);
}
}
}
// Room 에 모인 유저들과의 대전을 시작합니다.
// 방장이 Start 버튼을 누르면 이 함수가 호출된다고 가정하겠습니다.
void HandleStartMatch(const Ptr<Session> &session,
const string &user_name) {
// 방장이 아니면 이상한 클라이언트라고 가정하겠습니다.
if (user_name != master_user_name()) {
session->Close();
return;
}
Json message;
// 대전을 진행할 맵을 선택합니다. 여기서는 간단하게
// 100 ~ 200 사이의 값을 랜덤하게 선택하겠습니다.
int64_t map_id = RandomGenerator::GenerateNumber(100, 200);
// 대전 시간을 입력합니다. 여기서는 180 초라고 가정하겠습니다.
message["match_time"] = 180;
message["match_map_id"] = map_id;
// 대전 정보를 전송합니다.
for (const auto &v: sessions_) {
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (v.second->session->IsTransportAttached()) {
v.second->session->SendMessage("server_start_match", message);
}
}
// 여기서는 단순히 180 초가 지나면 대전이 끝난다고 가정하겠습니다.
// 만약 중간에라도 대전이 끝날 수 있다면 match_timer_id_ 를
// Timer::Cancel(match_timer_id) 함수를 호출하여
// 아래에서 등록하는 timer 를 취소시킬 수 있습니다.
// 180 초 이후에 대전을 종료시키는 timer 를 등록합니다.
match_timer_id_ =
Timer::ExpireAfter(
WallClock::FromMsec(180 * 1000),
bind(&Room::EndMatch, shared_from_this()));
}
// 대전을 종료합니다.
void EndMatch() {
// 승자와 패자 정보를 전송합니다.
Json message;
// ...
for (const auto &v: sessions_) {
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (v.second->session->IsTransportAttached()) {
v.second->session->SendMessage("server_end_match", message);
}
}
}
private:
Room(const Uuid &id, const string &name, const int64_t joinable_min_level,
const string &master_user_name)
: id_(id), name_(name), joinable_min_level_(joinable_min_level),
master_user_name_(master_user_name) {
}
// Room list 를 전달할 때 사용합니다. Json 으로 정보를 전달합니다.
Json GetInfo() const {
Json info;
info["name"] = name();
int64_t user_count = sessions_.size();
info["user_count"] = user_count;
info["joinable_min_level"] = joinable_min_level();
info["master_user_name"] = master_user_name();
return info;
}
// Room 의 고유 id 입니다.
const Uuid id_;
// Room 의 이름입니다. 이름은 변경 가능하다고 가정했습니다.
string name_;
// 방에 입장가능한 최소 레벨입니다. 최소 레벨은 변경 가능하다고 가정했습니다.
int64_t joinable_min_level_;
Timer::Id match_timer_id_;
// 방장 이름입니다.
string master_user_name_;
typedef boost::unordered_map<
SessionId, Ptr<User>, boost::hash<Uuid>> SessionMap;
SessionMap sessions_;
static boost::mutex the_mutex;
static RoomMap the_rooms;
};
DEFINE_CLASS_PTR(Room);
boost::mutex Room::the_mutex;
Room::RoomMap Room::the_rooms;
|
This is what is needed to implement functions:
class Room
id()
member function (units to synchronize)class Room
HandleJoin()
,HandleLeave()
,HandleChat()
,HandleStartMatch()
member function (executing events)
There are two items. To synchronize without a lock, the method calling the Handle… function is limited as handled below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | using funapi;
...
public class Room
{
public static Dictionary<System.Guid, Room> TheRooms =
new Dictionary<System.Guid, Room>();
struct User
{
public User(Session session, string name, ulong character_id, ulong level)
{
Session = session;
Name = name;
CharacterId = character_id;
Level = level;
}
public Session Session
{
get;set;
}
public string Name
{
get;set;
}
public ulong CharacterId
{
get;set;
}
public ulong Level
{
get;set;
}
}
void SendMessage(Session session, string msg_type, bool result)
{
JObject message = new JObject ();
message ["result"] = result;
session.SendMessage (msg_type, message);
return;
}
public static Room Create(
string name, ulong joinable_min_level, string master_user_name)
{
System.Guid id = RandomGenerator.GenerateUuid ();
Room room = new Room (id, name, joinable_min_level, master_user_name);
lock (TheRooms)
{
TheRooms.Add (id, room);
}
return room;
}
public static bool Find(System.Guid id, out Room room)
{
lock (TheRooms)
{
return TheRooms.TryGetValue (id, out room);
}
}
static JObject GetRooms()
{
JObject rooms = new JObject ();
lock (TheRooms)
{
foreach (var room in TheRooms)
{
string room_id = room.Key.ToString ();
rooms [room_id] = room.Value.GetInfo ();
}
}
return rooms;
}
public void HandleJoin(Session session, string name,
ulong character_id, ulong level)
{
// 유저가 Room 에 입장할 수 있는지 조건을 검사합니다.
// 이미 입장했었는지 확인합니다.
if (Sessions.ContainsKey (session.Id))
{
SendMessage (session, "client_join", false);
return;
}
// 이름이나 캐릭터 id 가 정상인지 확인합니다.
if (name == String.Empty || character_id < 0) {
SendMessage (session, "client_join", false);
return;
}
// 입장 가능한 최소 레벨을 만족하는지 검사합니다.
if (JoinableMinLevel < level) {
SendMessage (session, "client_join", false);
return;
}
// 모든 조건에 만족하였습니다. 이제 Room 에 입장시키도록 하겠습니다.
User user = new User (session, name, character_id, level);
Sessions.Add (session.Id, user);
// 성공적으로 입장했습니다.
// session context 에 room id 를 저장하여
// 메시지 핸들러에서 클라이언트가 room id 를 보내지 않아도
// session context 로부터 room id 를 가져와 사용하도록 하겠습니다.
session.AddToContext ("room_id", Id.ToString ());
foreach (var it in Sessions)
{
if (it.Value.Session.IsTransportAttached ())
{
it.Value.Session.SendMessage ("server_user_join", message, funapi.Session.Encryption.kDefault, funapi.Session.Transport.kTcp);
}
}
}
public void HandleLeave(Session session)
{
string user_name;
User user;
if (!Sessions.TryGetValue (session.Id, out user))
{
SendMessage (session, "client_leave", false);
return;
}
user_name = user.Name;
// Room 에서 유저를 삭제합니다.
Sessions.Remove (session.Id);
// 모든 유저가 Room 에서 나갔으므로 방을 빈방으로 처리하겠습니다.
// 여기서는 간단하게 name 만 지우면 빈방이 되겠다고 하겠습니다.
if (Sessions.Count == 0)
{
Name = "";
}
SendMessage (session, "client_leave", true);
// 떠나는 유저를 제외한 Room 내 유저들에게 메시지를 전송합니다.
JObject message = new JObject ();
message ["name"] = user_name;
foreach (var it in Sessions)
{
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (it.Value.Session.IsTransportAttached ())
{
it.Value.Session.SendMessage ("server_user_leave", message);
}
}
}
// Room 내 유저들과 채팅하는 것을 처리합니다.
public void HandleChat(Session session, string msg)
{
string user_name;
User user;
// Room 에 존재하지 않는 session(유저) 입니다.
if (!Sessions.TryGetValue (session.Id, out user))
{
SendMessage (session, "client_chat", false);
return;
}
user_name = user.Name;
// 유저 이름이 비어 있으면 안됩니다.
Log.Assert (user_name != String.Empty);
// Room 내 유저들에게 채팅 메시지를 전송합니다.
JObject message = new JObject ();
message ["name"] = user_name;
message ["msg"] = msg;
foreach (var it in Sessions)
{
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (it.Value.Session.IsTransportAttached ()) {
it.Value.Session.SendMessage ("server_user_chat", message);
}
}
}
// Room 에 모인 유저들과의 대전을 시작합니다.
// 방장이 Start 버튼을 누르면 이 함수가 호출된다고 가정하겠습니다.
public void HandleStartMatch(Session session, string user_name)
{
// 방장이 아니면 이상한 클라이언트라고 가정하겠습니다.
if (user_name != MasterUserName) {
session.Close();
return;
}
JObject message = new JObject();
// 대전을 진행할 맵을 선택합니다. 여기서는 간단하게
// 100 ~ 200 사이의 값을 랜덤하게 선택하겠습니다.
ulong map_id = (ulong) RandomGenerator.GenerateNumber(100, 200);
// 대전 시간을 입력합니다. 여기서는 180 초라고 가정하겠습니다.
message ["match_time"] = 180;
message ["match_map_id"] = map_id;
// 대전 정보를 전송합니다.
foreach (var it in Sessions)
{
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (it.Value.Session.IsTransportAttached ()) {
it.Value.Session.SendMessage ("server_start_match", message);
}
}
// 여기서는 단순히 180 초가 지나면 대전이 끝난다고 가정하겠습니다.
// 만약 중간에라도 대전이 끝날 수 있다면 match_timer_id_ 를
// Timer::Cancel(match_timer_id) 함수를 호출하여
// 아래에서 등록하는 timer 를 취소시킬 수 있습니다.
// 180 초 이후에 대전을 종료시키는 timer 를 등록합니다.
MatchTimerId =
Timer.ExpireAfter(
WallClock.FromMsec(180 * 1000), EndMatch);
}
// 대전을 종료합니다.
public void EndMatch(UInt64 tid, DateTime value)
{
// 승자와 패자 정보를 전송합니다.
JObject message = new JObject();
// ...
foreach (var it in Sessions)
{
// session 에 transport 가 연결되어 있을 경우에만 메시지를 전송.
if (it.Value.Session.IsTransportAttached ()) {
it.Value.Session.SendMessage ("server_end_match", message);
}
}
}
public ulong JoinableMinLevel
{
get;set;
}
public string MasterUserName
{
get;set;
}
public string Name
{
get;set;
}
public System.Guid Id
{
get;
}
Room(System.Guid id, string name,
ulong joinable_min_level, string master_user_name)
{
Id = id;
Name = name;
JoinableMinLevel = joinable_min_level;
MasterUserName = master_user_name;
}
JObject GetInfo()
{
JObject info = new JObject ();
info ["name"] = Name;
info ["user_count"] = Sessions.Count;
info ["joinable_min_level"] = JoinableMinLevel;
info ["master_user_name"] = MasterUserName;
info ["id"] = Id.ToString ();
return info;
}
Dictionary<System.Guid, User> Sessions =
new Dictionary<System.Guid, User>();
UInt64 MatchTimerId = 0;
}
|
51.2.2. Serializing events¶
Synchronize events to be handled in the room in each message handler.
src/event_handlers.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | void OnSessionOpened(const Ptr<Session> &session) {
}
// session 이 닫히면 호출됩니다.
// 이 예제에서는 session 이 닫힐 때만 Room 에서 나가도록 처리하겠습니다.
// tcp 연결이 끊겼을 때 호출되는 OnTcpTransportDetached() 함수에서는
// 방에서 나가는 처리를 하지 않겠습니다.
void OnSessionClosed(const Ptr<Session> &session, SessionCloseReason reason) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
return;
}
string room_id_str;
// session context 에서 Room id 를 가져옵니다.
if (not session->GetFromContext("room_id", &room_id_str)) {
// 방에 접속하지 않은 세션입니다.
return;
}
Uuid room_id = boost::lexical_cast<Uuid>(room_id_str);
Ptr<Room> room = Room::Find(room_id);
// 반드시 Room 이 존재한다고 가정했습니다.
BOOST_ASSERT(room);
// 유저를 로그아웃 처리합니다.
bool is_user_logged_out = AccountManager::SetLoggedOut(user_name);
// 반드시 로그아웃 처리된다고 가정하겠습니다.
BOOST_ASSERT(is_user_logged_out);
// Room id 로 이벤트를 직렬화합니다.
Event::Invoke([room, session]() {
room->HandleLeave(session);
}, room_id);
}
// tcp 연결이 끊어지면 이 함수가 호출됩니다.
// session 이 닫히는 것과는 다릅니다.
// tcp 연결이 끊겼어도 session 은 session timeout 이 지나지 않으면
// 닫히지 않습니다.
void OnTcpTransportDetached(const Ptr<Session> &session) {
// 여기서는 tcp 연결이 끊겨도 Room 에서 나가지 않도록 처리하겠습니다.
// session timeout 으로 session 이 닫히기 전에
// 재연결하게 될 경우 다시 게임을 진행할 수도 있기 때문입니다.
}
// 클라이언트 로그인을 처리합니다.
void OnClientLogin(const Ptr<Session> &session, const Json &message) {
// 유저 이름이 메시지에 포함되어 있는지 확인합니다.
if (not message.HasAttribute("user_name", Json::kString)) {
SendMessage(session, "client_login", false);
return;
}
// 유저 이름을 가져옵니다.
string user_name = message["user_name"].GetString();
// Object subsystem 을 이용하여
// user_name 에 해당하는 object 를 가져올 수 있습니다.
Ptr<User> user = User::FetchByName(user_name);
// 만약 유저가 존재하지 않으면 생성하겠습니다.
if (not user) {
user = User::Create(user_name);
// 이미 user_name 에 해당하는 User 가 생성되었다면 nullptr 를 반환합니다.
if (not user) {
SendMessage(session, "client_login", false);
return;
}
// 기본 레벨 1로 설정
user->SetLevel(1);
// 기본 캐릭터 아이디 1로 설정
user->SetCharacterId(1);
}
// user 가 반드시 존재한다고 가정했습니다.
BOOST_ASSERT(user);
// level 과 character id 를 가져옵니다.
int64_t user_level = user->GetLevel();
int64_t character_id = user->GetCharacterId();
// 필요하다면 다른 정보도 가져옵니다.
// ...
// 이제 AccountManager 를 이용하여 로그인 처리를 합니다.
// session 을 user_name 으로 mapping 할 수 있습니다.
// 이미 로그인 처리되어 있다면 false 가 리턴됩니다.
if (not AccountManager::CheckAndSetLoggedIn(user_name, session)) {
SendMessage(session, "client_login", false);
return;
}
// 유저에게 자신의 정보를 전달합니다.
Json response;
response["result"] = true;
response["user_level"] = user_level;
response["character_id"] = character_id;
session->SendMessage("client_login", response);
}
void OnClientLogout(const Ptr<Session> &session, const Json &message) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
SendMessage(session, "client_logout", false);
return;
}
// Room 에 입장할 때 session context 에 저장한 room id 를 가져옵니다.
string room_id_str;
if (not session->GetFromContext("room_id", &room_id_str)) {
// 만약 Room 에 입장하지 않았다면 room_id 가 없을 수 있습니다.
// 이 경우에는 단순히 로그아웃되었다고 처리합니다.
SendMessage(session, "client_logout", true);
return;
}
// 반드시 room id 가 저장되었다고 가정했습니다.
BOOST_ASSERT(not room_id_str.empty());
// 입장한 Room 에서 유저를 나가게 합니다.
Uuid room_id = boost::lexical_cast<Uuid>(room_id_str);
Ptr<Room> room = Room::Find(room_id);
// 반드시 Room 이 존재한다고 가정했습니다.
BOOST_ASSERT(room);
// 유저를 로그아웃 처리합니다.
bool is_user_logged_out = AccountManager::SetLoggedOut(user_name);
// 반드시 로그아웃 처리된다고 가정하겠습니다.
BOOST_ASSERT(is_user_logged_out);
// Room id 로 이벤트를 직렬화합니다.
Event::Invoke([room, session]() {
room->HandleLeave(session);
}, room_id);
}
void OnClientJoinRoom(const Ptr<Session> &session, const Json &message) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_join_room", false);
return;
}
Ptr<User> user = User::FetchByName(user_name);
// user object 가 존재한다고 가정하겠습니다.
BOOST_ASSERT(user);
// Room 입장 시 필요한 유저 정보를 가져옵니다.
int64_t character_id = user->GetCharacterId();
int64_t user_level = user->GetLevel();
// 만약 Room 을 새로 만드는 것이라면 room_id 는 메시지에 없습니다.
Ptr<Room> room;
if (not message.HasAttribute("room_id", Json::kString)) {
// 이 경우 Room 을 만들어야 하므로 room_name, joinable_min_level 이
// 메시지에 있어야 합니다.
if (not message.HasAttribute("room_name", Json::kString) ||
not message.HasAttribute("joinable_min_level", Json::kInteger)) {
// 필요한 값이 메시지에 없습니다.
// 이상한 클라이언트라 판단하고 세션을 닫겠습니다.
session->Close();
return;
}
// Room 생성에 필요한 값을 가져오고 Room 을 생성합니다.
const string room_name = message["room_name"].GetString();
const int64_t joinable_min_level = message["joinable_min_level"].GetInteger();
room = Room::Create(room_name, joinable_min_level, user_name);
BOOST_ASSERT(room);
} else {
// 기존에 존재하는 Room 에 입장하는 것이면 room_id 는 메시지에 있어야 합니다.
string room_id_str = message["room_id"].GetString();
// room_id 값이 비어있지 않다고 가정했습니다.
BOOST_ASSERT(not room_id_str.empty());
Uuid room_id = boost::lexical_cast<Uuid>(room_id_str);
room = Room::Find(room_id);
// Room 은 반드시 존재해야 합니다.
BOOST_ASSERT(room);
}
// Room 을 생성하거나 찾았습니다. 이제 Room 에 입장시키겠습니다.
// Room id 로 이벤트를 직렬화합니다.
Event::Invoke([room, session, user_name, character_id, user_level]() {
room->HandleJoin(session, user_name, character_id, user_level);
}, room->id());
}
void OnClientLeaveRoom(const Ptr<Session> &session, const Json &message) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_leave_room", false);
return;
}
// Room 에 입장할 때 session context 에 저장해둔 room id 를 가져오겠습니다.
string room_id_str;
if (not session->GetFromContext("room_id", &room_id_str)) {
// 방에 입장하지 않은 세션입니다.
// 이상한 클라이언트라고 가정하여 세션을 닫겠습니다.
session->Close();
return;
}
Uuid room_id = boost::lexical_cast<Uuid>(room_id_str);
Ptr<Room> room = Room::Find(room_id);
// 반드시 Room 이 존재한다고 가정하겠습니다.
BOOST_ASSERT(room);
// Room 에서 유저를 나가게 합니다.
// Room id 로 이벤트를 직렬화합니다.
Event::Invoke([room, session]() {
room->HandleLeave(session);
}, room_id);
}
void OnClientChat(const Ptr<Session> &session, const Json &message) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_chat_room", false);
return;
}
// chat msg 가 메시지에 없으면 이상한 클라이언트라고 가정하겠습니다.
if (not message.HasAttribute("chat_msg", Json::kString)) {
session->Close();
return;
}
string chat_msg = message["chat_msg"].GetString();
if (chat_msg.empty()) {
// 비어있는 chat msg 도 이상한 클라이언트라고 가정하겠습니다.
session->Close();
return;
}
// Room 에 입장할 때 session context 에 저장해둔 room id 를 가져오겠습니다.
string room_id_str;
if (not session->GetFromContext("room_id", &room_id_str)) {
// 방에 입장하지 않은 세션입니다.
// 이상한 클라이언트라고 가정하여 세션을 닫겠습니다.
session->Close();
return;
}
Uuid room_id = boost::lexical_cast<Uuid>(room_id_str);
Ptr<Room> room = Room::Find(room_id);
// 반드시 Room 이 존재한다고 가정하겠습니다.
BOOST_ASSERT(room);
// Room 내 유저들에게 chat msg 를 전달합니다.
// Room id 로 이벤트를 직렬화합니다.
Event::Invoke([room, session, chat_msg]() {
room->HandleChat(session, chat_msg);
}, room_id);
}
// 대전을 시작합니다. 방장만이 이 메시지를 보낼 수 있습니다.
void OnClientStartMatch(const Ptr<Session> &session, const Json &message) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_start_match", false);
return;
}
// Room 에 입장할 때 session context 에 저장해둔 room id 를 가져오겠습니다.
string room_id_str;
if (not session->GetFromContext("room_id", &room_id_str)) {
// 방에 입장하지 않은 세션입니다.
// 이상한 클라이언트라고 가정하여 세션을 닫겠습니다.
session->Close();
return;
}
Uuid room_id = boost::lexical_cast<Uuid>(room_id_str);
Ptr<Room> room = Room::Find(room_id);
// 반드시 Room 이 존재한다고 가정하겠습니다.
BOOST_ASSERT(room);
// 대전 처리를 합니다.
// Room id 로 이벤트를 직렬화합니다.
Event::Invoke([room, session, user_name]() {
// HandleStartMatch() 함수에서 방장 여부를 검사합니다.
room->HandleStartMatch(session, user_name);
}, room_id);
}
// 모든 Room 정보를 보냅니다.
void OnClientGetRoomList(const Ptr<Session> &session, const Json &message) {
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager::FindLocalAccount(session);
if (user_name.empty()) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_get_room_list", false);
return;
}
// 모든 Room 들의 정보를 Json 으로 가져와서 메시지를 전송합니다.
Json rooms = Room::GetRooms();
session->SendMessage("client_get_room_list", rooms);
}
// 다음과 같이 RegisterEventHandlers() 함수에서 메시지 핸들러를 등록합니다.
void RegisterEventHandlers() {
// OnSessionClosed() 함수가 호출되었을 때(즉 세션이 닫혔을 때)
// Room 에서 나가도록 작업하였습니다.
HandlerRegistry::Install2(OnSessionOpened, OnSessionClosed);
HandlerRegistry::Register("client_login", OnClientLogin);
HandlerRegistry::Register("client_logout", OnClientLogout);
HandlerRegistry::Register("client_join_room", OnClientJoinRoom);
HandlerRegistry::Register("client_leave_room", OnClientLeaveRoom);
HandlerRegistry::Register("client_chat", OnClientChat);
HandlerRegistry::Register("client_start_match", OnClientStartMatch);
HandlerRegistry::Register("client_get_room_list", OnClientGetRoomList);
// tcp 연결이 끊어지면 OnTcpTransportDetached 함수가 호출됩니다.
// session 이 닫히는 것과는 다릅니다.
// tcp 연결이 끊겼어도 session 은 session timeout 이 지나지 않으면
// 닫히지 않습니다.
// OnTcpTransportDetached() 함수가 호출되었을 땐 Room 에서 나가도록
// 작업하지 않았습니다.
HandlerRegistry::RegisterTcpTransportDetachedHandler(OnTcpTransportDetached);
}
|
Event::Invoke
is invoked in the OnClient...
message handler, but here
C++ 11 lambda
is passed as a callback.
All events executed in the room are sent through Event::Invoke
.
Synchronization takes place as follows:
Units to synchronize: event groups such as
room id
are executed in the orderEvent::Invoke
was invoked (later).If all events to be executed in the room are executed through
Event::Invoke
, a lock is not necessary when changing variables within the room.If
room id
differs (i.e. other rooms), all are executed concurrently.Use C++11 lambda as a callback. Enter variables required for lambda capture syntax (e.g. room, session) to access later in the code inside
Invoke
. (You can use lambda capture syntax ([=]
) if necessary.)
Note
If you used C++11 lambda here, you can use std::function
or boost::function
. However, if C++11 lambda can be used in the environment,
it is better to have external variables referred to in code executed asynchronously.
You can easily prevent bugs by designating whether to copy or refer inside the capture syntax.
For more details, see MSDN’s C++11 lambda page , See cppreference.com lambda page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | public static void SendMessage(Session session, string msg_type, bool result)
{
JObject message = new JObject ();
message ["result"] = result;
session.SendMessage (msg_type, message);
return;
}
public static void OnSessionOpened(Session session)
{
}
// session 이 닫히면 호출됩니다.
// 이 예제에서는 session 이 닫힐 때만 Room 에서 나가도록 처리하겠습니다.
// tcp 연결이 끊겼을 때 호출되는 OnTcpTransportDetached() 함수에서는
// 방에서 나가는 처리를 하지 않겠습니다.
public static void OnSessionClosed(
Session session, Session.CloseReason reason)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount (session);
if (user_name == String.Empty)
{
return;
}
string room_id_str;
// session context 에서 Room id 를 가져옵니다.
if (!session.GetFromContext ("room_id", out room_id_str))
{
// 방에 접속하지 않은 세션입니다.
return;
}
System.Guid room_id = new System.Guid (room_id_str);
Room room;
Room.Find (room_id, out room);
// 반드시 Room 이 존재한다고 가정했습니다.
Log.Assert (room != null);
bool is_user_logged_out = AccountManager.SetLoggedOut (user_name);
// 반드시 로그아웃 처리된다고 가정하겠습니다.
Log.Assert (is_user_logged_out);
// Room id 로 이벤트를 직렬화합니다.
Event.Invoke (() => {
room.HandleLeave(session);
}, room_id);
}
// tcp 연결이 끊어지면 이 함수가 호출됩니다.
// session 이 닫히는 것과는 다릅니다.
// tcp 연결이 끊겼어도 session 은 session timeout 이 지나지 않으면
// 닫히지 않습니다.
public static void OnTcpTransportDetached(
Session session)
{
// 여기서는 tcp 연결이 끊겨도 Room 에서 나가지 않도록 처리하겠습니다.
// session timeout 으로 session 이 닫히기 전에
// 재연결하게 될 경우 다시 게임을 진행할 수도 있기 때문입니다.
}
public static void OnClientLogin(Session session, JObject message)
{
if (message ["user_name"] == null)
{
SendMessage(session, "client_login", false);
return;
}
string user_name = (string) message ["user_name"];
User user = User.FetchByName (user_name);
// 만약 유저가 존재하지 않으면 생성하겠습니다.
if (user == null)
{
user = User.Create(user_name);
// 이미 user_name 에 해당하는 User 가 생성되었다면 null 을 반환합니다.
if (user == null)
{
SendMessage(session, "client_login", false);
return;
}
// 기본 레벨 1로 설정
user.SetLevel (1);
// 기본 캐릭터 아이디 1로 설정
user.SetCharacterId (1);
// user 가 반드시 존재한다고 가정했습니다.
Log.Assert (user != null);
}
// level 과 character id 를 가져옵니다.
Int64 user_level = user.GetLevel ();
Int64 character_id = user.GetCharacterId ();
// 필요하다면 다른 정보도 가져옵니다.
// ...
// 이제 AccountManager 를 이용하여 로그인 처리를 합니다.
// session 을 user_name 으로 mapping 할 수 있습니다.
// 이미 로그인 처리되어 있다면 false 가 리턴됩니다.
if (!AccountManager.CheckAndSetLoggedIn (user_name, session))
{
SendMessage (session, "client_login", false);
return;
}
// 유저에게 자신의 정보를 전달합니다.
JObject response = new JObject();
response ["result"] = true;
response ["user_level"] = user_level;
response ["character_id"] = character_id;
session.SendMessage ("client_login", response);
}
public static void OnClientLogout(Session session, JObject message)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount (session);
if (user_name == String.Empty)
{
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage (session, "client_logout", false);
return;
}
// Room 에 입장할 때 session context 에 저장한 room id 를 가져옵니다.
string room_id_str;
if (!session.GetFromContext ("room_id", out room_id_str))
{
// 만약 Room 에 입장하지 않았다면 room_id 가 없을 수 있습니다.
// 이 경우에는 단순히 로그아웃되었다고 처리합니다.
SendMessage (session, "client_logout", true);
return;
}
Log.Assert (room_id_str != String.Empty);
System.Guid room_id = new System.Guid (room_id_str);
// 반드시 Room 이 존재한다고 가정했습니다.
Room room = null;
Log.Assert (Room.Find (room_id, out room));
Log.Assert (room != null);
// 유저를 로그아웃 처리합니다.
bool is_user_logged_out = AccountManager.SetLoggedOut (user_name);
// 반드시 로그아웃 처리된다고 가정하겠습니다.
Log.Assert (is_user_logged_out);
// Room id 로 이벤트를 직렬화합니다
Event.Invoke (() => {
room.HandleLeave (session);
}, room_id);
}
public static void OnClientJoinRoom(Session session, JObject message)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount (session);
if (user_name == String.Empty)
{
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage (session, "client_join_room", false);
return;
}
User user = User.FetchByName(user_name);
// user object 가 존재한다고 가정하겠습니다.
Log.Assert (user != null);
// Room 입장 시 필요한 유저 정보를 가져옵니다.
ulong user_level = (ulong) user.GetLevel ();
ulong character_id = (ulong) user.GetCharacterId ();
// 만약 Room 을 새로 만드는 것이라면 room_id 는 메시지에 없습니다.
Room room;
if (message ["room_id"] == null)
{
// 이 경우 Room 을 만들어야 하므로 room_name, joinable_min_level 이
// 메시지에 있어야 합니다.
if (message ["room_name"] == null ||
message ["joinable_min_level"] == null)
{
// 필요한 값이 메시지에 없습니다.
// 이상한 클라이언트라 판단하고 세션을 닫겠습니다.
session.Close();
return;
}
// Room 생성에 필요한 값을 가져오고 Room 을 생성합니다.
string room_name = (string) message ["room_name"];
ulong joinable_min_level = (ulong) message ["joinable_min_level"];
room = Room.Create(room_name, joinable_min_level, user_name);
Log.Assert (room != null);
}
else
{
// 기존에 존재하는 Room 에 입장하는 것이면 room_id 는 메시지에 있어야 합니다.
string room_id_str = (string) message ["room_id"];
// room_id 값이 비어있지 않다고 가정했습니다.
Log.Assert(room_id_str != String.Empty);
System.Guid room_id = new System.Guid (room_id_str);
room = null;
// Room 은 반드시 존재해야 합니다.
Log.Assert (Room.Find (room_id, out room));
Log.Assert (room != null);
}
// Room 을 생성하거나 찾았습니다. 이제 Room 에 입장시키겠습니다.
// Room id 로 이벤트를 직렬화합니다.
Event.Invoke (() => {
room.HandleJoin (session, user_name, character_id, user_level);
}, room.Id);
}
public static void OnClientLeaveRoom(Session session, JObject message)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount (session);
if (user_name == String.Empty)
{
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage (session, "client_leave_room", false);
return;
}
// Room 에 입장할 때 session context 에 저장해둔 room id 를 가져오겠습니다.
string room_id_str;
if (!session.GetFromContext ("room_id", out room_id_str))
{
// 방에 입장하지 않은 세션입니다.
// 이상한 클라이언트라고 가정하여 세션을 닫겠습니다.
session.Close();
return;
}
// room_id 값이 비어있지 않다고 가정했습니다.
Log.Assert(room_id_str != String.Empty);
System.Guid room_id = new System.Guid (room_id_str);
Room room = null;
// Room 은 반드시 존재해야 합니다.
Log.Assert (Room.Find (room_id, out room));
Log.Assert (room != null);
// Room 에서 유저를 나가게 합니다.
// Room id 로 이벤트를 직렬화합니다.
Event.Invoke (() => {
room.HandleLeave(session);
}, room.Id);
}
public static void OnClientChat(Session session, JObject message)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount (session);
if (user_name == String.Empty)
{
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage (session, "client_leave_room", false);
return;
}
// chat msg 가 메시지에 없으면 이상한 클라이언트라고 가정하겠습니다.
if (message ["chat_msg"] == null)
{
session.Close();
return;
}
string chat_msg = (string) message ["chat_msg"];
if (chat_msg == String.Empty)
{
// 비어있는 chat msg 도 이상한 클라이언트라고 가정하겠습니다.
session.Close ();
return;
}
// Room 에 입장할 때 session context 에 저장해둔 room id 를 가져오겠습니다.
string room_id_str;
if (!session.GetFromContext ("room_id", out room_id_str))
{
// 방에 입장하지 않은 세션입니다.
// 이상한 클라이언트라고 가정하여 세션을 닫겠습니다.
session.Close ();
return;
}
Log.Assert (room_id_str != String.Empty);
System.Guid room_id = new System.Guid (room_id_str);
Room room = null;
// Room 은 반드시 존재해야 합니다.
Log.Assert (Room.Find (room_id, out room));
Log.Assert (room != null);
// Room 내 유저들에게 chat msg 를 전달합니다.
// Room id 로 이벤트를 직렬화합니다.
Event.Invoke (() => {
room.HandleChat (session, chat_msg);
}, room_id);
}
// 대전을 시작합니다. 방장만이 이 메시지를 보낼 수 있습니다.
public static void OnClientStartMatch(Session session, JObject message)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount(session);
if (user_name == String.Empty) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_start_match", false);
return;
}
// Room 에 입장할 때 session context 에 저장해둔 room id 를 가져오겠습니다.
string room_id_str;
if (!session.GetFromContext("room_id", out room_id_str)) {
// 방에 입장하지 않은 세션입니다.
// 이상한 클라이언트라고 가정하여 세션을 닫겠습니다.
session.Close();
return;
}
System.Guid room_id = new System.Guid(room_id_str);
Room room = null;
// 반드시 Room 이 존재한다고 가정하겠습니다.
Log.Assert (Room.Find(room_id, out room));
Log.Assert (room != null);
// 대전 처리를 합니다.
// Room id 로 이벤트를 직렬화합니다.
Event.Invoke (() => {
// HandleStartMatch() 함수에서 방장 여부를 검사합니다.
room.HandleStartMatch(session, user_name);
}, room_id);
}
// 모든 Room 정보를 보냅니다.
public static void OnClientGetRoomList(Session session, JObject message)
{
// 이 서버에 로그인한 유저인지 확인합니다.
string user_name = AccountManager.FindLocalAccount(session);
if (user_name == String.Empty) {
// 로그인한 유저가 아니면 실패 처리합니다.
SendMessage(session, "client_start_match", false);
return;
}
// 모든 Room 들의 정보를 Json 으로 가져와서 메시지를 전송합니다.
JObject rooms = Room.GetRooms();
session.SendMessage("client_get_room_list", rooms);
}
public static void Install(ArgumentMap arguments)
{
...
NetworkHandlerRegistry.RegisterMessageHandler ("client_login",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientLogin));
NetworkHandlerRegistry.RegisterMessageHandler ("client_logout",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientLogout));
NetworkHandlerRegistry.RegisterMessageHandler ("client_chat_room",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientChat));
NetworkHandlerRegistry.RegisterMessageHandler ("client_join_room",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientJoinRoom));
NetworkHandlerRegistry.RegisterMessageHandler ("client_leave_room",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientLeaveRoom));
NetworkHandlerRegistry.RegisterMessageHandler ("client_start_match",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientStartMatch));
NetworkHandlerRegistry.RegisterMessageHandler ("client_get_room_list",
new NetworkHandlerRegistry.JsonMessageHandler (OnClientGetRoomList));
|
Warning
Here, we did not deal with leaving the room when ending a TCP connection. Depending on the situation, battles may be maintained when leaving a session and then reconnecting even if TCP was disconnected. (Handled so that when a session is closed, the room is only left in the invoked OnSessionClosed() function.)
If, when the TCP connection is also closed, room departure must be handled or AI
must replace the user in battle,
the HandlerRegistry::RegisterTcpTransportDetachedHandler()
function
can be added to handle TCP disconnection.