Mercurial > hg > Papers > 2017 > kazuma-thesis
view presen/slide.md @ 12:10a1f30eb748
Update English abstruct.
author | Kazuma Takeda |
---|---|
date | Wed, 15 Feb 2017 09:28:06 +0900 |
parents | 207fa0b0c3a2 |
children | 7b39f43e4088 |
line wrap: on
line source
title: ゲームエンジンにおけるJungleDatabaseの提案 author: Kazuma Takeda profile: lang: Japanese code-engine: coderay # この発表のセクション - RDBとNoSQL - Jungle Databseの提案 - Jungleの仕様 - ゲームのデータ構造 - Jungle-Sharpの実装 - 例題ゲームの実装 - Jungle-Sharpの改良点 # RDBとNoSQL Relational Databseと呼ばれるRDBは行と列からなる2次元のテーブルにより実装されているデータベース。 データ型として文字列や数値、日付、Bool型がある。 データの一貫性を重視しているRDBでは分散システムには向いていない。 NoSQL(Not Only SQL) Databaseと呼ばれる非リレーショナル型のデータベース。 スキームを持たないため、扱うデータの型を気にしなくてもよい。 一貫性を一部犠牲にしているNoSQLでは分散させることが可能である。 CassandraやMongoDBなどが例に挙げられる。 # インピーダンスミスマッチ プログラム中ではListやネスト構造によりデータを扱うことができる。 しかしデータベースにはそのような概念はない。 そこにプログラムとデータベースの間にギャップが生じる。 これをインピーダンスミスマッチという。 RDBではネスト構造を許さない第一正規形とは相容れない。 # NoSQLのトランザクション CassandraやほとんどのNoSQLではACIDなトランザクションがない。 トランザクション中の処理は外部からは閲覧出来ない。 しかし、複数行を1回で書き換える機能を持っていないため、 データを書き込んでいる途中の状態が見えてしまう場合がある。 # Jungle Databaseの提案 前章までRDBではプログラムとのミスマッチや分散構造に向いていない問題、NoSQLではトランザクションでの問題点について触れた。 これらの問題を解決するため、当研究室で開発しているデータベースJungleを提案する。 Jungleは過去の変更データを保存しつつ新しい木を構築してく木構造(非破壊構造)の手法をとる。 非破壊にすることにより、データを読み出す側と書き込む側のデータを安全に扱うことができる。 Jungleは名前で管理された木のあつまりからなる。 木は複数のノードの集合からなる。 ノード自身にはKey-Valueのデータを格納することができる。 これはデータベースのレコードに相当する。 Jungleのトランザクションはルートから変更を行うノードまでコピーを行い、新しく木構造を構築する。 最後にルートをアトミックに入れ替えてコミットする。 コミットが失敗した場合は最初からやり直す。 これにより、原子性を実現する。 Jungleはcommit logを持ち、それを他のノードやディスクに転送することにより、 分散構成と永続性を実現する。 # JungleのAPI 前章ではJungleの概要を記述した。 本章ではJungleの主なAPIについて紹介する。 # Jungleの木 Jungleは複数の木を名前を利用して管理しており、名前により作成・編集を行う。 ``` Java // Jungleに新しく木を生成する。木の名前が重複した場合、生成に失敗しnullを返す JungleTree createNewTree(string treeName) // JungleからtreeNameと名前が一致するtreeを取得する。名前が一致するTreeがない場合取得は失敗しnullを返す JungleTree getTreeByName(string treeName) ``` 以下のコードは、Jungleの木を"TreeName"で生成し取得する。 ``` Java JungleTree tree = jungle.createNewTree("GameTree"); ``` # TreeNode Jungleが保持している木は、複数のノードの集合で出来ている。 ノードは、自身の子のListと属性名と属性値の組でデータを持つ。 ノードに対するアクセスは、TreeNodeクラスに記述されているAPIを用いて行われる。 ``` Java // ノードの子供を扱うChildrenオブジェクトを返す Children getChildren() // ノードが保持しているデータを扱うAttribteオブジェクトを返す Attribute getAttribute() ``` # ChildrenとAttribute Childrenクラスを利用し、ノードの子どもにアクセスする。 ``` Java // ノードが持っている子どもの個数を返す int size() // ノードが持つ子どもの中から、 変数numで指定された位置にある子ノードを返す Either<Error, TreeNode> at(int num) ``` Attributeクラスを利用し、ノードの保持する値にアクセスする。 ``` Java // ノードからKeyで管理されるValueをobject型で返す object get(string key) // ノードからKeyで管理されるValueをstring型で返す string getString(string key) ``` # Eitherクラス Jungleでは例外がある場合、Eitherクラスを用いて行う。 - 失敗時はA - 成功時はB を包んで返す。 失敗した場合ははじめからやり直す。 以下に例を記述する。 ``` C\# Either<Error,TreeNode> either = children.at(2); if (either.isA()) return either.a(); TreeNode child = either.b(); ``` # Jungleのサンプルコード Jungleの例を記載する。 以下のコードは、ルートノードの2番目の子どもから、属性名"name"とペアになっている属性値を取得する。 ``` C\# JungleTree tree = jungle.getTreeByName("GameTree"); TreeNode root = tree.getRootNode(); Children children = root.getChildren(); Either<Error,TreeNode> either = children.at(2); if (either.isA()) return either.a(); TreeNode child = either.b(); Attribute attribute = child.getAttribute(); string value = attribute.getstring("name"); ``` # Jungleの木の編集 Jungleの木の編集はJungleTreeEditorクラスを用いて行われる。 JungleTreeEditorクラスには編集を行うために、定義されているAPIを記述する。 また、ノードを指定して編集を行う際にNodePathクラスを用いる。 # NodePath Jungleでは、木のノードの位置をNodePathクラスを使って表す。 NodePathクラスはルートノードからスタートし、対象のノードまでの経路を、数字を用いて指し示すことで対象のノードの場所を表す。 また、ルートノードは例外として-1と表記される。 <div style="text-align: center;"> <img src="./images/NodePath.pdf" alt="message" width="400"> </div> # ノードの追加 ``` C\# // 変数pathで指定した場所にある、ノードの子供の変数posで指定した位置子ノードを追加 Either<Error, JungleTreeEditor> addNewChildAt( NodePath path, int pos) // 変数pathで指定した場所にあるノードに、属性名 変数key 属性値 変数valueのペアで値を挿入 Either<Error, JungleTreeEditor> putAttribute( NodePath path, string key, object value) // 変数pathで指定した場所にあるノードが持つ、属性名 変数keyとペアで保存されているデータを削除 Either< Error, JungleTreeEditor> deleteAttribute( NodePath path, string key) ``` # コミット 編集の最後にTreeに対してコミットを行う。 ``` C\# // 木へ行った変更をコミット。自分が編集を行っていた間に、他のJungleTreeEditorクラスによって木が更新されていた場合、コミットは失敗 Either<Error, JungleTreeEditor> commit() ``` # ゲームのデータ構造 Jungleはもともと認証管理システムやWeb向けに作られたものである。 これらはすべて木構造をベースとしている。 ゲームでも同じことが考えられる。 そこでゲームエンジンUnity向けにJungleの再実装を行い、ゲーム向けのデータベースとしての提案を行う。 Unityは3Dゲームエンジンで、ゲームを構成する要素(Object)をC\#で制御する。 Objectは一つのゲームのシーン(一画面の状況)の中で木構造を持つ。 これをシーングラフと言う。 シーングラフをそのままJungleに格納するという手法が考えられる。 # Jungle-Sharpの実装 JungleはもともとJavaとHaskellで書かれていた。 今回はJava版をベースにC\#で再実装する。 エラーをチェックするEitherの部分だけはHaskellの要素を取ってくる。 # Atomic Refarenceの実装 Jungleの木の変更(commit)はCAS(Check and Set)を用いてatomicに行われる。 競合している書き込み中に自分の書き込みが成功した場合に関数commit()が成功する。 失敗した場合ははじめからもう一度行う。 JavaのモジュールにはAtomicRefarenceが存在した。 C\#では自分で作る必要があった。 ``` C\# // C\# public bool CompareAndSet(T newValue, T prevValue) { T oldValue = value; return (oldValue != Interlocked.CompareExchange (ref value, newValue, prevValue)); } ``` # Listの実装 木やリストをたどる時にJavaではIteratorを用いる。 Iteratorは次の値があるかを返すboolean hasNext()と、Tという型の次の値を取ってくるT next()を持つObjectである。 C\#では木やリストを辿りながらyeildで次の値を返す。 Javaでは以下のように実装されている。 ``` Java public Iterator<T> iterator() { return new Iterator<T>() { Node<T> currentNode = head.getNext(); @Override public boolean hasNext() { return currentNode.getAttribute() != null; } @Override public T next() { T attribute = currentNode.getAttribute(); currentNode = currentNode.getNext(); return attribute; } }; } ``` # Listの実装 C\#ではそもそも匿名クラスの中でメソッドを定義できない。 この場合はIEnuratorを使って書き直すことができた。 ``` C\# // C\# public IEnumerator<T> iterator() { Node<T> currentNode = head.getNext(); while (currentNode.getAttribute() != null) { yield return (T)currentNode.getAttribute(); currentNode = currentNode.getNext (); } } ``` # Eitherのチェック Haskellでは例外処理はモナド内部で行う設計になっている。 Eitherもその一つである。 Jungleではある処理に対してエラーであればA、 なければBをEitherに包んで返す。 JavaのJungleでは分岐を使ってチェックする必要があった。 ``` Java // Java Either<Error,TreeNode> either = children.at(2); if (either.isA()) return either.a(); TreeNode child = either.b(); ``` # bindの実装 Eitherクラスに実装したbindは自身のEitherをチェックした後、 エラーがなければ関数fを実行し評価する仕組みである。 ```C\# public Either<A, B> bind (System.Func<B, Either<A, B>> f) { if (this.isA ()) { return this; } return f (this.b ()); } ``` ユーザー側でのエラーのチェックは不要になるが、関数fのLambda式を自分で定義する必要がある。 次のページにその例を示す。 # bindの引数に渡すラムダ式の例 ``` C\# Either<Error, JungleTreeEditor> either = DefaultEither<Error, JungleTreeEditor>.newB(editor); Item apple = new Item("Apple"); either = either.bind ((JungleTreeEditor arg) => { return arg.putAttribute (rootNode, item.name, item); }); ``` # 例題のゲーム 前章ではJungle-Sharpのどのように実装したかを述べた。 この章では実際にゲームを構築し、そのデータベースとしてJungleを導入する。 今回作ったゲームはMinecraftの簡易版である。 <div style="text-align: center;"> <img src="./images/craft.png" alt="message" width="400"> </div> プレイヤーは自由にマップを移動し、ステージの破壊や、生成を行うことができる。 破壊や生成のオペレーションに合わせてJungleのノードにも同期する。 # ゲームデータの種類 ゲームのデータにはいくつかの種類が考えられる。 ## オブジェクトが単体で持つデータ シーン内に存在するオブジェクトが持つパラメータ。 例えば、プレイヤーのHPや経験値、位置座標などを示す。 ## オブジェクト1つで複数持つデータ プレイヤーが持つアイテムデータなどを示す。 ## マスタデータ(ReadOnly) アイテムの名前や敵の出現確率などを示す。 ゲーム開発者のみが更新できる。 # データのデータ設計 Jungleには複数の木を持つことができる。 ゲームのシーンを構成するGameTreeとアイテムを管理するItemTreeをJungle内に作る。 # GameTree GameTreeではシーン内にあるPlayerやStageを構成するCubeなどを格納している。 Jungleではオブジェクトが単体で持つデータと、オブジェクト一つで複数持つデータを同時に表現できる。 以下にその例を示す。 <div style="text-align: center;"> <img src="./images/Tree.pdf" alt="message" width="600"> </div> # ItemTree ItemTreeではItemデータを格納している。 データの種類ではマスターデータにあたいする。 以下にその例を示す。 <div style="text-align: center;"> <img src="./images/ItemTree.pdf" alt="message" width="800"> </div> # Jungleの改良 前章では例題となるゲームを作成した。 その上でJungleではデータ型について問題となった。 C\#の再実装を行った際にJavaのJungleに沿ってデータの型、つまりByteArrayで設計を行っていた。 データの格納を行うたびにByte Arrayへのキャストを行う必要がある。 しかし、キャストの処理は軽くはない。 そこで、シーンを構成するObjectをそのまま格納するに仕様を変更した。 C\#ではObjectクラスのエイリアスとしてobject型が使える。 ``` C\# Player player = new Player (); either = either.bind ((JungleTreeEditor arg) => { return arg.putAttribute ("Player", player); }); Enemy enemy = new Enemy (); either = either.bind ((JungleTreeEditor arg) => { return arg.putAttribute ("Enemy", enemy); }); ``` # データを取り出す データを取り出すにはGenericで型を指定する、もしくはas演算子を用いてキャストを行う。 以下に取り出す例を記述する。 ``` C\# Player player = attr.get<Player> ("Player"); Enemy enemy = attr.get ("Enemy") as Enemy; ``` データの型の再設計を行ったことによりシーン内のオブジェクトをそのまま格納が可能になった。 格納の際にByte Arrayに変換する必要がない。 分散構造や、ネットワークで必要な時だけ変換する。 # まとめ 本研究の流れは - Jungle-Sharpの実装 - UnityでのApplicationの実装 - 問題点の改良 である。 Jungle-Sharpの実装ではJavaと比較的似ている言語であるため、移行する方法を確立した。 C\#版のJungleではJavaに劣らない、もしくはそれ以上のパフォーマンスを出すことが出来た。 実際のゲームに合わせたJungleの拡張を行った。 データの格納の際にByteBufferであったものをObject型に変更した。 これにより、シーンを構成するObjectデータを手間なく格納することを可能にした。 Jungleは非破壊であるため、過去の変更を持っている。 ゲームにおいて過去の木を持ち続けることはパフォーマンスの低下につながる。 そのため、過去の木をどこまで必要かを検討しなければならない。 現在C\#版のJungleにはデータを永続化させる仕組みは備わっていない。 実用的なゲームのデータベースとして使うためには永続化を実装する必要がある。