なんとなく

誰得感満載な記事が多いかも。Mono関係とLinuxのサーバ関係、レビューとか。

libopenmetaverseでフレンドリストを表示してみる

はじめに

2012年のアドベントカレンダーからlibopenmetaverse関連の事を紹介してきて、この時期じゃないとlibopenmetaverseについて触れていない気がします。libopenmetaverse カテゴリーの記事一覧 - なんとなく

やらなくちゃと自分自身に厳しくできるので、こういう企画は好きです。

さて、 セカンドライフ技術系 Advent Calendar 2014 - Adventarの1個目のエントリです。 当初、プロフィールを表示することをネタにしようと思って書き始めたのですが、長くなってしまいました。完走させるためにも2部構成にしようと思います。 なので、1個目のエントリです。

概要

全体的な流れとしては、フレンドリストの対象項目(青で囲んだところ)を押下したら、ダイアログで対象のアバターのプロフィールを表示させるということを説明していきたいと思います。とりあえず、今回はフレンドリストの作成です。

f:id:takeshich:20141222161653p:plain

f:id:takeshich:20141222161346p:plain

libopenmetaverseはC#で書かれているのに、Androidなの?と疑問を持つ方もいらっしゃるかもしれません。 Xamarin.Androidで実現しています。

フレンドリストの作成

画像の左側のリスト作成の説明です。

使用するlibopenmetaverseのメンバーの説明とか

libopenmetaverseにおいて、アバターのフレンドリストは、OpenMetaverse.FriendsManager.FriendListというプロパティに格納されています。

public InternalDictionary<UUID, FriendInfo> FriendList

と定義されています。

http://lib.openmetaverse.org/docs/trunk/?topic=html/F_OpenMetaverse_FriendsManager_FriendList.htm

FriendInfoクラスは

Member Description
CanModifyMyObjects True if the freind can modify my objects
CanModifyTheirObjects True if I can modify my friend's objects
CanSeeMeOnline True if the friend can see if I am online
CanSeeMeOnMap True if the friend can see me on the map
CanSeeThemOnline True if I can see if my friend is online
CanSeeThemOnMap True if I can see if my friend is on the map
IsOnline True if the avatar is online
MyFriendRights My rights represented as bitmapped flags
Name full name of the avatar
TheirFriendRights My friend's rights represented as bitmapped flags
ToString() FriendInfo represented as a string(Overrides Object.ToString().)
UUID System ID of the avatar

と定義されています。

http://lib.openmetaverse.org/docs/trunk/?topic=html/T_OpenMetaverse_FriendInfo.htm

InternalDictionaryもlibopenmetaverse独自のクラスのようです。

http://lib.openmetaverse.org/docs/trunk/?topic=html/T_OpenMetaverse_InternalDictionary_2.htm

フレンドリストは以上を使って取得できます。今回はUUIDとIsOnlineを取得し、Nameについては取得しません。 Nameにはついて、2010年ごろにDisplayNameが実装されたため、DisplayNameに対応していません。 そのため別途DisplayNameの取得をする必要があります。

そこで、DisplayNameは、 OpenMetaverse.AvatarManagerGetDisplayNamesというメッソッドで取得出来ます。

public void GetDisplayNames(
    List<UUID> ids,
    AvatarManager.DisplayNamesCallback callback
)

と定義されています。

http://lib.openmetaverse.org/docs/trunk/?topic=html/M_OpenMetaverse_AvatarManager_GetDisplayNames.htm

DisplayNamesCallbackは

public delegate void DisplayNamesCallback(
    bool success,
    AgentDisplayName[] names,
    UUID[] badIDs
)

と定義されています。

http://lib.openmetaverse.org/docs/trunk/?topic=html/T_OpenMetaverse_AvatarManager_DisplayNamesCallback.htm

AgentDisplayNameクラスは、

Member Description
AgentDisplayName() Initializes a new instance of the AgentDisplayName class
DisplayName Display name
FromOSD(OSD) Creates AgentDisplayName object from OSD
GetOSD() Return object as OSD map
ID Agent UUID
IsDefaultDisplayName Is display name default display name
LegacyFirstName First name (legacy)
LegacyFullName Full name (legacy)
LegacyLastName Last name (legacy)
NextUpdate Cache display name until
ToString() (Overrides Object.ToString().)
Updated Last updated timestamp
UserName Username

と定義されています。

http://lib.openmetaverse.org/docs/trunk/?topic=html/T_OpenMetaverse_AgentDisplayName.htm

GetDisplayNamesを使用し、DisplayNamesCallbackで得られるAgentDisplayName[]からDisplayNameとUserNameを取得します。

なお、DisplayNameの仕様としてUsernames and display names - Second Lifeにおいて、

You can choose whether or not to show other Residents' display names and usernames inworld. If you choose to view display names or usernames, they appear in the name tags above every avatar and in chat.

とあるように、 displaynameを表示させるのかusernameを表示させるのか選択できるので、それにしたがって表示を対応させます。

IsDefaultDisplayNameを使って判断します。

http://lib.openmetaverse.org/docs/trunk/?topic=html/F_OpenMetaverse_AgentDisplayName_IsDefaultDisplayName.htm

また、そもそもdisplaynameを設定していない場合もあり、いわゆるレガシーなfullnameを取得します。 OpenMetaverse.AvatarManager.RequestAvatarNamesを使用します。以前にも紹介したと思いますので、URIを参照ください。

http://lib.openmetaverse.org/docs/trunk/?topic=html/E_OpenMetaverse_AvatarManager_UUIDNameReply.htm

実装

省きながらではあるけど、実際にどういうふうに実装しているかについて見て行きたいと思います。ソースコードとその説明です。

ソース
List<UUID> Buddy = new List<UUID>();

//フレンドリスト取得
Client.Friends.FriendList.ForEach(delegate(FriendInfo friendInfo){
    //UUIDとIsOnlineの値をList<AvatarData>な入れ物に格納
    BuddyIDisOnline.Add(new AvatarData(friendInfo.UUID,null,null,false,friendInfo.IsOnline));
    Buddy.Add(friendInfo.UUID);
});

//DisplayName取得、取れない場合は名前を取得する
DisplayNamesDone = new AutoResetEvent (false);
Client.Avatars.GetDisplayNames(Buddy,BuddyDisplayNamesCallback);
DisplayNamesDone.WaitOne();
Client.Avatars.RequestAvatarNames(BuddyBadIDs);
BuddyBadIDs.Clear();

//List<AvatarData>な入れ物をUUIDをキーにjoinして、オンラインでソートし、asciiコード順でソート
var query = from s in BuddyIDName
    join t in BuddyIDisOnline
    on s.AvatarID equals t.AvatarID
    orderby t.IsOnline descending,s.AvatarName ascending
    select new {AvatarID= s.AvatarID,AvatarName = s.AvatarName,AvatarDisplayName = s.AvatarDisplayName,IsDefaultDisplayName = s.IsDefaultDisplayName,IsOnline = t.IsOnline};

BuddyIDNameisOnline.Clear();

//adapterに入れるものに入れなおす
foreach(var t in query)
{
    BuddyIDNameisOnline.Add(new AvatarData(t.AvatarID,t.AvatarName,t.AvatarDisplayName,t.IsDefaultDisplayName,t.IsOnline));
}

BuddyIDName.Clear();
BuddyIDisOnline.Clear();

イベントハンドラやコールバックなど

Client.Avatars.UUIDNameReply += (object sender, UUIDNameReplyEventArgs e) => {
    foreach (KeyValuePair<UUID,string> kvp in e.Names) {
        //UUIDとNAMEを取得
        BuddyIDName.Add(new AvatarData(kvp.Key,kvp.Value,null,false));
    }
};

DisplayNamesCallback += new AvatarManager.DisplayNamesCallback (BuddyDisplayNamesCallback);

コールバックの実態

private void BuddyDisplayNamesCallback(bool success,AgentDisplayName[] DisplayNames,UUID[] BadIDs)
{

    //得られたUUID,username(takeshich.nakamura),DisplayName,IsDefaultDisplayName(デフォルトの表示をDisplayNameにするか)を格納
    foreach(AgentDisplayName displayname in DisplayNames)
    {
        BuddyIDName.Add(new AvatarData(displayname.ID,displayname.UserName,displayname.DisplayName,displayname.IsDefaultDisplayName));
    }

    //DisplayNameを得られなかったものについて取得
    foreach (UUID s in BadIDs)
    {
        BuddyBadIDs.Add(s);
    }

}
実装説明
  • DisplayNameを取得するため、FriendListにおいてFriendInfoクラスのUUIDをListに格納します。
  • オンラインかについて判断したいので、FriendInfoクラスのUUIDとIsOnlineをカスタムクラスに格納します。
  • DisplayNameを取得します。
  • UUID,usename(例Takeshich.Nakamura),DisplayName,IsDefaultDisplayName(デフォルトの表示をDisplayNameにするか)を格納しています。
  • DisplayNameを得られなかったものについて、UUIDとNAMEを格納しています。
  • それぞれを格納したものをUUIDをキーにjoinして、リストを表示させるために並び順を変更します。
  • 再度ListAdapterに入れるための入れ物に入れ直します。

f:id:takeshich:20141222161343p:plain

フレンドリストのうちオンラインのアバターを先に表示(UserName,DisplayName)し、さらに赤い文字にしています。 そして、それらをasciiコード順で表示、オフラインのアバターについてもasciiコード順で表示しています。

まとめ

ざっと説明してきました。Xamarin.Androidの視点からみるところは、省いてたりします。。。(ListViewにcustomadapterいれるところとか、Dialogを表示するために必要な値の渡し方とか)

まぁ、Viewerの中ではこんな感じなんだよということを少しでも感じていただければと思います。 これでフレンドリストが作成できたので、アバターのUUIDを使って、プロフィールを取得し、表示するということを次回見ていくことにします。