部屋を開く

handleOpenRoom

OpenRoomRequest を受け取ると、handleOpenRoom が呼ばれる。 ここに部屋を開く処理を記述する。


bool SampleRoomModule::handleOpenRoom(Room* _pRoom, OpenRoomRequest* pMsg, ResponseMessage* pRes, SessionHandle& hSession)
{
   if (! SampleOpenRoomRequest::Check(pMsg)) { return false; }
   SampleRoom *pRoom = static_cast< SampleRoom* >(_pRoom);

   pRoom->lock();
   {
      if (! pRoom->open(pMsg)) { goto err; }
      if (! pRoom->enterOwner(hSession)) { goto err; }

      if (pRoom->countMembers() == pRoom->getMemberSize()) {
         if (! pRoom->match()) {
            pRoom->leave(hSession);
            goto err;
         }
      }
   }
   pRoom->unlock();

   {
      EnterRoomNotify *p = new EnterRoomNotify();
      p->domainIndex = hSession.domain()->index();
      p->roomId = pRoom->id();
      hSession.session()->queueMessage(p);
   }
   notifyRoom(pRoom);
   return true;

  err:
   pRes->setResult(ResponseMessage::ERR_INTERNAL);
   pRoom->close();
   pRoom->unlock();
   return false;
}

SampleOpenRoomRequest

handleOpenRoom の最初のところで、SampleOpenRoomRequest の static 関数 Check を呼んでいる。 この SampleOpenRoomRequest クラスは、OpenRoomRequest の拡張を扱うためのヘルパークラスである。

SampleOpenRoomRequest は sampleMessage.h に定義されている。


class SampleOpenRoomRequest
{
      typedef nine::match::OpenRoomRequest MSG;
   public:
      inline static bool Check(MSG* pMsg) {
         return (pMsg->extIsExist(1) && pMsg->extIsExist(2));
      }
      inline static void SetPassword(MSG* pMsg, const char pw[20]) { pMsg->extSetArray(1, pw, 20); }
      inline static void GetPassword(const MSG* pMsg, char pw[20]) { pMsg->extGetArray(1, pw, 20); }
      inline static void SetRule1(MSG* pMsg, uint8_t rule) { pMsg->extSetUInt8(2, rule); }
      inline static void GetRule1(const MSG* pMsg, uint8_t *rule) { pMsg->extGetUInt8(2, rule); }
};

まずは SetPassword, SetRule1 といったセッター関数をみてみると、 MessageExtension機能を利用して、MSGすなわち OpenRoomMessage を拡張しているのがわかる。

わざわざヘルパー関数を定義しているのは、1,2 といった拡張ID を管理するためである。OpenRoomMessage を使うところでこの関数を使うようにすれば、拡張のIDや型の不整合によるバグに悩まされずに済む。

そして Check() 関数は、OpenRoomRequest メッセージがこれらの拡張を持っているかどうかを判定するヘルパー関数である。 サンプルでは、Password, Rule1 の二つの拡張を必須としているので、そのようなチェックコードを実装している。

開室処理

lock/unlock に囲まれた部分が部屋を開く部分である。


   pRoom->lock();
   {
      if (! pRoom->open(pMsg)) { goto err; }
      if (! pRoom->enterOwner(hSession)) { goto err; }

      if (pRoom->countMembers() == pRoom->getMemberSize()) {
         if (! pRoom->match()) {
            pRoom->leave(hSession);
            goto err;
         }
      }
   }
   pRoom->unlock();

ブロックの前半は、Room の open と enterOwner の二つの関数を順番に呼んでいるだけである。

後半は if ブロックで何やら書いているが、これは一人部屋の場合のマッチング完了処理を行っている。 コードの意味はここでは説明しない。後の マッチング処理を読めば理解できるはずである。

さて、open() は部屋を開く処理を行う関数で、enterOwner() は部屋のオーナーとして部屋に入る関数である。関数の詳細は次節で説明する。

ここでは、部屋を開いた後で部屋に入る処理を記述していることになる。マッチングの仕様としては、部屋を開くだけで入らないケースもあり得る。その場合は enter 処理は書かなければ良い。

このように、単機能を実装した関数を Room で実装し、RoomModule ではそれら単機能を組み合わせるような責任分担で設計することを推奨する。

SampleRoom::open()

open では実際に部屋を開く処理を記述する。

bool SampleRoom::open(const OpenRoomRequest* pMsg)
{
   lock();
   {
      if (! stateOpen()) { goto err; }
      SampleRoomMatchInfo* p = static_cast< SampleRoomMatchInfo* >(mutableMatchInfo());
      p->title = pMsg->roomInfo.title;
      SampleOpenRoomRequest::GetPassword(pMsg, p->password);
      SampleOpenRoomRequest::GetRule1(pMsg, &p->rule1);
   }
   unlock();
   return true;

  err:
   unlock();
   return false;
}

stateOpen() は Room ベースクラスで定義されている protected 関数で、部屋を開室状態へと移す。 部屋を開く際には必ずこの関数を呼ぶ必要がある。

続く部分はルームのプロパティ SampleRoomMatchInfo に、部屋を開く時の情報をセットしている。 セットしている情報は、上で述べた OpenRoomRequest メッセージの拡張としてクライアントから渡される。

SampleRoomMatchInfo はフレームワーク提供の RoomMatchInfo を継承したクラスで、Room::createMatchInfo() ファクトリ関数で使用を指定している。

struct SampleRoomMatchInfo: public RoomMatchInfo
{
      char    password[20];
      uint8_t rule1;
};

RoomMatchInfo* SampleRoom::createMatchInfo() const
{
  return new SampleRoomMatchInfo();
}

SampleRoom::enterOwner

enterOwner は単純にルームメンバーに追加しているだけである。

bool SampleRoom::enterOwner(const SessionHandle& hSession)
{
   lock();
   bool b = addMember(hSession);
   unlock();
   return b;
}

addMember は Room ベースクラスで定義されていて、部屋にメンバーを追加する際に使用する。

後で述べるように、入室にはパスワードチェックがかかっている。 部屋を開いた時のオーナーはこのチェックが不要なので、専用の関数を提供している。

EnterRoomNotify

サンプルルームの仕様では部屋を開くと同時に入室するので、EnterRoomNotify を通知しなければならない。

また、入室した部屋の情報を伝える必要もある。

handleOpenRoom では、部屋を開く処理をした後で次のようなコードが続く。

bool SampleRoomModule::handleOpenRoom(Room* _pRoom, OpenRoomRequest* pMsg, ResponseMessage* pRes, SessionHandle& hSession)
{
   ...

   {
      EnterRoomNotify *p = new EnterRoomNotify();
      p->domainIndex = hSession.domain()->index();
      p->roomId = pRoom->id();
      hSession.session()->queueMessage(p);
   }
   notifyRoom(pRoom);

   ...
}

ブロックの部分で、EnterRoomNotify を送信している。 EnterRoomNotify をセットアップして、MatchSession#queueMessage で送信キューに入れている。 Notify を送る処理は大体このようなコードになる。

最後の notifyRoom は、部屋のメンバー全員に部屋情報 RoomInfoNotify を送る関数である。

void SampleRoomModule::notifyRoom(const SampleRoom* pRoom)
{
   const std::vector< const SampleRoomMemberInfo* > v = pRoom->getSampleMembers();
   for (int i=0; i<v.size(); ++i) {
      if (v[i]->hSession.isNull()) { continue; }

      RoomInfoNotify* pMsg = new RoomInfoNotify();
      SetupRoomInfo(pMsg->roomInfo, pRoom, pRoom->id());
      pMsg->lobbyId = 0xFFFF;
      v[i]->hSession.session()->queueMessage(pMsg);
   }
}