Last-modified: Mon, 03 Jan 2005 00:15:01 JST
[dynamic,cache:on]
powered by tds-1.6.2
09:00 起床, 09:50 会社着。
なかなか一筋縄ではいかないが、 それなりに見えるものを作ってみる。
この手の機能は、 設計段階から意識してシステムを作っておけば実現できる見込みがあるが、 後から追加するのは極めて難しい。 随時、メンテナンスしておくのが吉。
該当箇所を絞り込んで、 その部分をメンテナンスしている人間に投げ。 後でコードレビューした方が良さそうだ。
この数日、メモリがらみの問題が立て続けに出たけど、 やっぱり手作業でメモリの確保・解放を行ったり、 グローバル変数使うのは良くないという結論に達しそう。 ハンドラやスマートポインタの類を使い、 デストラクタで勝手に後始末するよう徹底してる人のところでは、 リークが一度も発生していないし。
20:10 離脱。
あの後 boost::function と boost::lambda::new_ptr を使って書き直したのですが、 「boost::function 使うとヒープ使うしイマイチだよなぁ」と思ってました。
ありがたく利用させて頂きます。
amzaon で注文していた 21st Century Compilers。 いつになっても発送の連絡がこないと思ったら発売延期 (2004/08/31) なのね。 ひとまず注文キャンセル。
代わりに Knuth 御大の書籍でも買うかね。
09:00 起床, 10:00 会社着。
新しくプロジェクトに来た人の環境整備。 前にドキュメントを書いておいたので、 それを読みつつサクサクと設定。
本来断片化しないはずなのに、微妙に断片化してるのが気になったので調査。 不要なアライメント指定してる場所を見つけて潰す。
あとはメモリアロケータで best fit 使ってる部分を first fit に書き直す準備。 テスト中に別の用事が入ったので、続きは明日。
してるみたいなので調べておいてね、と担当者に連絡しておいた件。
std::vector は clear() を読んでもメモリを解放しない。
という罠にはまっていたので、 swap() trick を使って解決。
STL を使う人間には、 全員 Effective STL を読んでもらった方が良いのかな。 会社の私物本棚には英語版 Effective STL があるが、 これを読んでもらうのはキツすぎなので邦訳を用意する方向で。
企画の人の環境で、特定のタイミングでプログラムが停止するという報告。 適当に当たりをつけて、とりあえず機能を落として止まらない環境を作る。 企画側は、これでしばらく作業継続してもらうことに。
その間に本格的な原因究明。 再現条件を詰めた感じ、 どうもうちのプロジェクトの問題ではなさそうな気がしてきた。 さて、どうしたものかね。
私の作業用 Windows PC が……。 最近、一日一回ぐらい落ちてる気がするな。 暖かくなってきたから?
機材関係の人に投げる方向で。
進捗報告。 微妙な感じだ。
私の関わる部分、プログラミングに関わる必要項目洗い出し。 これ以上は具体的な方向性が見えないとシステムの作りようがないため、 とりあえずコンテ切ってもらって、それを元に話を詰める方向で。
そろそろライトの使い方もまじめに考える時期だ。 見直しかけよう。
21:00.
こんな感じです。
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/construct.hpp>
struct IFoo { virtual ~IFoo() {} };
class Foo1 : public IFoo { public: Foo1(int) {} };
class Foo2 : public IFoo { public: Foo2(int) {} };
enum PRIO_TYPE { PRIO_LOW, PRIO_NORMAL, PRIO_HIGH };
enum FOO_TYPE { FOO1, FOO2, FOO3 };
void addQueue(IFoo* foo, PRIO_TYPE prio);
void Create(int id)
{
struct create_tbl
{
FOO_TYPE foo_type;
boost::function<IFoo* (int)> creator;
int param;
PRIO_TYPE prio;
};
using boost::lambda::new_ptr;
static create_tbl const db[] = {
{ FOO1, new_ptr<Foo1>(), 0, PRIO_LOW },
{ FOO2, new_ptr<Foo2>(), 0, PRIO_NORMAL },
{ FOO3, new_ptr<Foo2>(), 1, PRIO_LOW },
};
for (size_t i = 0; i < sizeof(db) / sizeof(db[0]); ++i) {
create_tbl const* p = &db[i];
if (p->foo_type == id) {
IFoo* foo = (p->creator)(p->param);
addQueue(foo, p->prio);
return;
}
}
}
09:15 起床, 10:00 会社着。
メインプログラマを捕まえて、 年単位の作業優先順位決め。 ま、だいたい見えた。
一つ近い内に手をつけようかと思っていたことは、 しばらく先送りで良いや。 ただ実装は後の時期にならなと手をつけられないが、 そのための下準備は今からしておかないと困るという扱いが微妙な代物で、 どうスケジュールに織り込んでいくかは未だ考慮中。
エフェクトをきれいに見せるためには、 画面を暗くすると良い。 というわけで、そのへんのシステム作り。
フェードアウト要求などはスクリプトから指示させる形にするが、 問題は競合する要求をどう捌くか。 これはシーンの全体設計がもう少し見えてこないと、 何とも言えないな。
デザイナさんの作業環境関係で何件か。 サクッと済ませる。
ハードウェア一部交換。
特定の条件でプログラムが止まる件。 これは IOP 側のメモリが断片化してる気がする。 調査しようにも、 プログラムが死んでしまうのでどうにもならんが。
20:40 離脱。
反戦小説と言われているけど、そうなの? ヘルシングとかベルセルクを読んで面白いと言えるなら、 これも文句なしに楽しめると思うけど。
12:00 起床。
ビーフ, 虚空100, ライス大盛り, スープ大盛り。 食後にココナッツプリン。 新作デザートらしい。
10:50 起床。
別に出勤してるワケじゃないけど、思いつきメモ。
現在のスクリプトシステムは、 スクリプト中から C++ 側の関数を呼び出せるようになっている。 そのための枠組みは次の通り。
スクリプト側
// #system の直後にシステム関数番号を書く
// システム関数番号を省略すると、直前のシステム関数の次の番号が割り当てられる
// #usertype は型チェックの厳しい typedef みたいなもの
#define SYSFUNC_ISSEI_START 512
/*
* デバッグメッセージ出力
*/
#system SYSFUNC_ISSEI_START void dbPrint(char);
/*
* プレイヤーを透明状態に移行
* 引数
* pl 透明状態に移行するプレイヤーオブジェクト
* time 透明状態継続時間
*/
#usertype handle player_t
#system void plSetInvisible(player_t pl, float time);
int
main(player_t pl)
{
plSetInvisible(pl, 1.0F);
}
C++ 側
static int
plSetInvisible(Script* scr)
{
// scr->getSystemArgument(n) で n 番目の引数取得
// union なので、適当な型のフィールドを持ってきて使う
Player* pl = reinterpret_cast<Script*>(scr->getSystemArgument(0).h);
pl->scr_SetInvisible(scr->getSystemArgument(1).f);
return 0;
}
int
script_init()
{
Script::SetSystemFunction(513, &plSetInvisible);
}
問題点
いろいろ考えていたのだけど、 スクリプトシステム関数定義ファイルから
を自動生成すれば良いんじゃないかという結論に。 さらにスクリプトから操作するオブジェクト側が実装すべきメンバ関数一覧も生成できるから、
とすると、労せずしてインターフェースと実装を分離できる。
考えがまとまったところで、 ちゃちゃっと Perl でスクリプト書き。 本物のスクリプトコンパイラは lex + yacc で書いてあるけど、 システム関数定義の解析程度なら perl の方が良いだろう。
微妙に面倒なのはシステム関数番号の解析ぐらい。 ここは単なる整数値とは限らず四則演算が出てくる可能性があるので、 自前で数式を解析・計算する必要がある。
だいたい動いたかな。 実際のスクリプト処理系のコードなどは会社に行かないとアクセスできないので、 明日組み込もう。
scrsysfunc.h (スクリプトシステム関数定義)
/** * スクリプト共通定義 * $Issei: scrsysfunc.h,v 1.1 2004/03/14 05:43:29 issei Exp $ */ #ifndef __SCRSYSFUNC_H #define __SCRSYSFUNC_H #define SYSFUNC_ISSEI_BEGIN 1024 #define SYSFUNC_ISSEI_DB_MAX 64 #define SYSFUNC_ISSEI_BTPLACT_MAX 128 #define SYSFUNC_ISSEI_BTPL_MAX 128 #define SYSFUNC_ISSEI_DB_BEGIN SYSFUNC_ISSEI_BEGIN #define SYSFUNC_ISSEI_BTPLACT_BEGIN (SYSFUNC_ISSEI_DB_BEGIN + SYSFUNC_ISSEI_BTPLACT_MAX) #define SYSFUNC_ISSEI_BTPL_BEGIN (SYSFUNC_ISSEI_BTPLACT_BEGIN + SYSFUNC_ISSEI_BTPL_MAX) #endif // !__SCRSYSFUNC_H
bt_pl_act.h (スクリプトシステム関数定義)
/** * プレイヤー制御関連スクリプト * $Issei: bt_pl_act.h,v 1.4 2004/03/14 05:39:01 issei Exp $ */ #ifndef __BT_PL_ACT_H #define __BT_PL_ACT_H #include "scrsysfunc.h" /* * デバッグ用 */ #system SYSFUNC_ISSEI_DB_BEGIN void dbPrintf(int); /* * プレイヤーアクションスクリプト */ #usertype handle bt_pl_act_t [isBattle::Player::ICtxScriptAction, isbt_pl_ctx_scr_action.h] #usertype uint hittype_t #system SYSFUNC_ISSEI_BTPLACT_BEGIN void btPlAct_createFrontCollision(bt_pl_act_t acthdl, hittype_t hit_type) #system int btPlAct_createFrontCollision2(bt_pl_act_t acthdl, handle foo, hittype_t hit_type) #system void btPlAct_requireCamera(bt_pl_act_t acthdl) #system void btPlAct_releaseCamera(bt_pl_act_t acthdl) #system void btPlAI_setActionString(bt_pl_act_t acthdl, char name) /* * プレイヤー基本制御スクリプト */ #usertype handle bt_pl_ai_t [isBattle::IPlayer] #usertype uint hp_t #system SYSFUNC_ISSEI_BTPL_BEGIN hp_t btPlAI_getHP(bt_pl_ai_t aihdl) #system hp_t btPlAI_getMaxHP(bt_pl_ai_t aihdl) #endif // !__BT_PL_ACT_H
script_bt_pl_act.cc (フリー関数とシステム関数登録処理)
// DO NOT EDIT
// THIS FILE IS GENERATED AUTOMATICALLY BY `aksc_cpp.pl 1.5'
#include "script.h"
#include "isbt_pl_ctx_scr_action.h"
static int
btPlAct_createFrontCollision(SCRIPT* scr)
{
isBattle::Player::ICtxScriptAction* p =
reinterpret_cast<isBattle::Player::ICtxScriptAction*>(scr->getSystemArgument(0).h);
p->scr_btPlAct_createFrontCollision(
scr->getSystemArgument(1).u
);
return 0;
}
static int
btPlAct_createFrontCollision2(SCRIPT* scr)
{
isBattle::Player::ICtxScriptAction* p =
reinterpret_cast<isBattle::Player::ICtxScriptAction*>(scr->getSystemArgument(0).h);
s32 ret = p->scr_btPlAct_createFrontCollision2(
scr->getSystemArgument(1).h,
scr->getSystemArgument(2).u
);
scr->setReturnValue(*reinterpret_cast<SCRIPT_REG*>(&ret));
return 0;
}
static int
btPlAct_requireCamera(SCRIPT* scr)
{
isBattle::Player::ICtxScriptAction* p =
reinterpret_cast<isBattle::Player::ICtxScriptAction*>(scr->getSystemArgument(0).h);
p->scr_btPlAct_requireCamera(
);
return 0;
}
static int
btPlAct_releaseCamera(SCRIPT* scr)
{
isBattle::Player::ICtxScriptAction* p =
reinterpret_cast<isBattle::Player::ICtxScriptAction*>(scr->getSystemArgument(0).h);
p->scr_btPlAct_releaseCamera(
);
return 0;
}
static int
btPlAI_setActionString(SCRIPT* scr)
{
isBattle::Player::ICtxScriptAction* p =
reinterpret_cast<isBattle::Player::ICtxScriptAction*>(scr->getSystemArgument(0).h);
p->scr_btPlAI_setActionString(
scr->getSystemArgument(1).c
);
return 0;
}
static int
btPlAI_getHP(SCRIPT* scr)
{
isBattle::IPlayer* p =
reinterpret_cast<isBattle::IPlayer*>(scr->getSystemArgument(0).h);
u32 ret = p->scr_btPlAI_getHP(
);
scr->setReturnValue(*reinterpret_cast<SCRIPT_REG*>(&ret));
return 0;
}
static int
btPlAI_getMaxHP(SCRIPT* scr)
{
isBattle::IPlayer* p =
reinterpret_cast<isBattle::IPlayer*>(scr->getSystemArgument(0).h);
u32 ret = p->scr_btPlAI_getMaxHP(
);
scr->setReturnValue(*reinterpret_cast<SCRIPT_REG*>(&ret));
return 0;
}
void
SCRIPT_bt_pl_act()
{
SCRIPT::setSystemFunction(1152, &btPlAct_createFrontCollision);
SCRIPT::setSystemFunction(1153, &btPlAct_createFrontCollision2);
SCRIPT::setSystemFunction(1154, &btPlAct_requireCamera);
SCRIPT::setSystemFunction(1155, &btPlAct_releaseCamera);
SCRIPT::setSystemFunction(1156, &btPlAI_setActionString);
SCRIPT::setSystemFunction(1280, &btPlAI_getHP);
SCRIPT::setSystemFunction(1281, &btPlAI_getMaxHP);
}
isbt_pl_ctx_scr_action.h (スクリプトから呼び出すメンバ関数一覧)
// DO NOT EDIT
// THIS FILE IS GENERATED AUTOMATICALLY BY `aksc_cpp.pl 1.5'
#ifndef __ISBT_PL_CTX_SCR_ACTION_H
#define __ISBT_PL_CTX_SCR_ACTION_H
namespace isBattle { namespace Player {
struct ICtxScriptAction
{
virtual ~ICtxScriptAction() {}
virtual void btPlAct_createFrontCollision(u32 hit_type) = 0;
virtual s32 btPlAct_createFrontCollision2(void* foo, u32 hit_type) = 0;
virtual void btPlAct_requireCamera() = 0;
virtual void btPlAct_releaseCamera() = 0;
virtual void btPlAI_setActionString(char const* name) = 0;
};
}}
#endif // !__ISBT_PL_CTX_SCR_ACTION_H
Windows なら IDL でインターフェース定義を書いておけば、 言語に依らず宜しくできるんだけどね。 本格的な IDL のパーサや COM 実行環境を用意するのはさすがにオーバースペックなので、 この辺で適当にお茶を濁しておく。
あ……、isbt_pl_ctx_scr_action.h の純粋仮想関数、 名前に scr_ つけ忘れてる。 面倒なので、全部明日送り。
行ってきます。
1km 泳いで帰宅。人が多すぎ。
私が書くとしたら、こうかな。
#include <algorithm>
#include <iostream>
#include <vector>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
class hoge
{
public:
virtual ~hoge() {}
virtual void show() = 0;
};
class uni : public hoge
{
public:
void show(){ std::cout << "uni" << std::endl;}
};
class unya : public hoge
{
public:
void show(){ std::cout << "unya" << std::endl;}
};
int main()
{
typedef boost::shared_ptr<hoge> THogePtr;
std::vector<THogePtr> v;
v.push_back(THogePtr(new uni));
v.push_back(THogePtr(new unya));
std::for_each(v.begin(), v.end(), boost::bind(&hoge::show, _1));
return 0;
}
boost::shared_ptr だと問題ないのですが、 一般には多態を使う場合、 基底クラスで仮想デストラクタを定義しておかないと極めて不幸なことになるので注意して下さい。 あと <, > あたりが正しく <, > になってないようで、 ブラウザで見ると vector の後が未知のタグ <hoge> と見なされて欠落してます。
09:00 起床, 10:00 会社着。
昨日 書いてたヤツ。 スクリプト担当してる人間に概要を説明して、 組み込み。 よしよし、うまく動いてるな。
「どこにあるか知りませんか?」と聞かれて、思い当たる節があったので一緒に探す。 灯台下暗し。
デザイナの作業の進め方について確認。 とりあえず、それで。
ディレクションする予定の人とプログラマで、 立ち話しつつ意識のすりあわせ。
どうも認識にズレがあるのではないかと危惧していた事があったのだけど、 やっぱりズレていた模様。 修正を図る。
どうしてもプロジェクトに最初から居て、 プロジェクトの紆余曲折・中核メンバーの為人を知っている人間と、 途中から参加した人間では、 持っている情報の量も違うし、 新規に同じ情報を入手しても解釈の精度に差が出てきてしまう。 それを見つけて埋めていくのも、まぁ仕事だ。
サブプログラマの人がスクリプトを使って作り込んでいるのだけど、 かなり良い感じになってきてる。 やるじゃん。
しかし、そろそろ C++ のコードを書きたい様子がチラチラと……。 ごめん。
21:50 離脱。 遅すぎ。
08:50 起床, 10:00 会社着。
常に増して断片化された時間を過ごす。 絶え間なく到来する要件を捌いていたら、 一日終わりという感じ。
20:30 離脱。
09:00 起床, 10:00 会社着。
例の C++ 側の関数自動生成スクリプトの都合に合わせて、 スクリプトコンパイラ自体に少し手を入れる。
ビルド時のログをファイルに残すように細工。 コマンド実行部分を $(SHELL) -c "..." | tee log と書き直したのだけど、 うっかり exit ${PIPESTATUS[0]} をつけ忘れて、 tee の終了ステータスを拾うミス発生。
気づいたところでさっさと修正。
さらに二人。 一件はメインプログラマに任せて、もう一件は私が対処。
「パスフレーズなしの ssh 秘密鍵を作ったのに、ログイン時にパスワードを聞かれます」 「~/.ssh と ~/.ssh/authorized_keys のグループ書き込み権限を確認せよ」
日程確定。
長らく TODO リストに載っていた複合型コリジョンを実装。 球・円柱といった基本的な形状のコリジョンをまとめて、 1 つのコリジョンとして扱うための仕組み。
サブプログラマに任せてあるヒットオブジェクト関連で、 いくつか設計上の相談。
20:30 離脱。
09:00 起床, 10:00 会社着。
もう使ってないが、 コンパイルを通すためだけに残っていたコードをざっくり切り捨て。
プログラマの進捗確認と、 その場でスケジュールの調整。
私は作業が先行してるので、 月末から来月頭あたりは微妙な感じだ。 私の手伝いをお願いしていたサブプログラマの人には、 先行して作業出来る部分に回ってもらおう。
動きアリ。よしよし。
単に絵素材を動かすだけのビューアなら描画部分を担当している人にお願いできるが、 ゲーム中のルールに従って動くエフェクトを動かすとなると、 ゲーム部分を書いている人間が書くしかない。
私のコードはインターフェースを細かく分けてあるので、 エフェクトを動かすだけなら純粋仮想関数 5 個だけ定義したクラスを継承し、 それに対して適切な挙動をすれば OK。 「適切な挙動」を実装するにも既存のパーツを使い回せるから、 とりあえず動かすだけなら今日明日で終わるだろう。
その先は、 ゲームシステムに関する理解を深めてもらう意味も兼ねて、 サブプログラマに任せる方向で。 いきなり現状のシステム全部見ようとすると疲れるから、 まずはサブセットのエフェクトビューアぐらいで遊んでもらう。
お手伝い。
しょせんゲームだから、 論文を書けそうな意欲的な技術テーマに対して正面から挑む必要はない。 むしろアイデアの本質的な面白さだけ抽出して、 技術的には大したことがない手法で実現できるように作り替えてしまった方が望ましい。 根本的なアイデアを出したり企画書を書くのは手伝えないけど、 思いつき程度のアイデア出し・技術的な可否の問い合わせ・代替策作りに関しては助言します。
とまぁ、そんなスタンスで仕事してる今日この頃。 アイデア出しを手伝う以外に、 どんなに素晴らしいアイデアであっても結局はプログラマが実装できるものしか実現できないので、 無理なものは早めにその旨を伝えておく意味もある。
しかし企画陣の方でも人によって重視する部分が微妙に違うようで、 マネージメントする人はお疲れ様という感じ。 技術的な話なら技術的にケリがつくけど、 企画案やマーケティングのデータ解釈といった本質的に不確実性を含むものに対して判断を下すのは難しい。 また競合する案が二つある場合、「明確にこちらの方が良い」と示せない状況で一方の案を却下することになるから、 やり方によっては痼りを残す。
私が思いつき程度のネタしか出さないのは、 単にアイデア不足・本業はプログラミングという理由もあるが、 このあたりにも理由がある。 私が真剣に自分のアイデアを暖めていると、 相手も「ちょっと相談なんだけど」と気軽に話を持ってこられなくなるし。
20:40 離脱。
ドトールで買ってきたコーヒー豆を開封。 それなり。
ようやく読了。 この手の本は読み終わったところが「終わり」ではなく「出発点」だから、 読了したからどうということはなく。
09:00 起床, 10:00 出社。
7.5 release. これまでは Pure Java アプリケーションとして提供されてきたが、 今回は Windows 版限定で native バイナリもあるみたい。 native バイナリを試してみたけど、 起動が遅いが起動してしまえば快適。