ゲームプログラマになる前に覚えておきたい技術
ラベル: プログラミング
ラベル: プログラミング
ラベル: プログラミング
class testclass
{
public:
testclass(const std::string& s): m_string(s) {}
void print_string() { std::cout << m_string << "\n"; }
private:
std::string m_string;
};
using namespace luabind;
module(L)
[
class_<testclass>("testclass")
.def(constructor<const std::string&>())
.def("print_string", &testclass::print_string)
];
ラベル: プログラミング
そのついででFortranについて調べたけど、この言語、嫌い。 コンピュータ黎明期に誰かが適当に作ってそのまま既成事実になってるんじゃないか と思うような書式が嫌だ。歴史的なことをいうと、FORTRAN は世界で初めて作られた高級言語です。
ラベル: プログラミング
この前提の下、さらに使用するハードウェアは低価格な汎用品、具体的には一般的なPCとローカルHDD, あとは Fast Ethernet, GbE の組み合わせて実現したいというリクエストあり。
利用特性を見ると、確かに一般的なファイルシステムへの要求とはずいぶんと違っている。
汎用のファイルシステム、たとえば NTFS とか FFFS だと、既存の API (Win32 API, POSIX) をサポートすることや大量の小さなファイルを効率的に扱うことやレイテンシなどが重要になる一方で、同時に複数クライアントから追記要求がきた場合に効率的に処理することなどは想定外。また、対障害性などはファイルシステムレベルでも当然考慮はするが、ある程度以上となったら、その下のハードウェア層でもRAIDを組むなどの対策を組むことが前提となる。
私は分散ファイルシステムはあまり知らないのだけど、ローカルファイルシステムのセマンティクスをなるべく崩さずにネットワーク対応させた NFS, CIFS なんかとは明らかに違うし、最初から壊れるマシンがいること前提なあたり AFS とも大分違う。対故障性や帯域重視というと RDBMS も考えられるが、今度は想定されるワークロードにズレがある。
paper では、以上の前提を述べた上で GFS のアーキテクチャと設計について解説している。前提が違うので、結果としてアーキテクチャもラディカルに違ったものになっているが、利用している個々の技術やアルゴリズムはきわめて一般的。読んで難しいところは何もない。
アーキテクチャ設計とバランス感覚が秀逸だね。個人的には、こういうシステム好きだ。
追記
読みかけの「Distributed Systems: Principles and Paradigms 2nd Ed.」 Chaper 11 で分散ファイルシステムが取り上げられているので、ちょっと途中スキップして覗いてみたら 11.1.2 Cluster-Based Distributed File Systems で Google File System (GFS) 取り上げてる。
タネンバウム本に載ってるということは、分散屋さんの間ではもはや常識なのか。
ラベル: プログラミング
ラベル: プログラミング
ラベル: プログラミング
push %eaxIA32 のコードをハンドアセンブルする気にはなれなかったので、それっぽいコードを書いて GNU as でアセンブルし、出力を逆アセンブルして thunk の雛形に。
movl 8(%esp), %eax ; %eaxをスタックに退避した分だけ +4 ずれてる。
movl %eax, pThis->m_hWnd
pop %eax
movl pThis, 4(%esp)
jmp WndProc
ラベル: プログラミング
あたくし思うに、ロックというのは本来はエラー処理なんかと同様に、プログラムの本質とは何にも関係ない部分なんだから、たとえば C++ の例外のように、プログラミング言語のレベルでもっと裏に隠せるような良い仕組みを用意できねえもんかなぁと、そんな気がしますな。最近パラパラと paper 漁った感じだと Software Transactional Memory が有望な感じです (参考)。
ラベル: プログラミング
FPU を kernel 内で使いたいのでコード書いてみたけど、trap 22 が起きて使えず困ってるという話が。
1121 5060 32459 i386/isa/npx.c
580 3135 18245 amd64/amd64/fpu.c
ラベル: プログラミング
ラベル: プログラミング
ラベル: プログラミング
/*
* 変更前
*/
node_primary_expression:
TOK_INT {
vm_int_t val = boost::any_cast<vm_int_t>($1);
p_comp-<genCodePUSH_I(val);
$$ = Cast::INT;
}
node_assignment_expression:
node_primary_expression
| node_assignment_expression '+' node_assignment_expression: {
p_comp->genOprADD_I();
$$ = $1;
}
/*
* 変更後
*/
node_primary_expression:
TOK_INT {
vm_int_t val = boost::any_cast<vm_int_t>($1);
ExpressionInfoP expr(new ExpressionInfo(val));
expr->addGenCode(boost::lambda::bind(&Comp::genCodePUSH_I, boost::lambda::_1, val));
$$ = expr;
}
node_assignment_expression:
node_primary_expression
| node_assignment_expression '+' node_assignment_expression
{
// 定数同士の加算の場合は、この場で計算して結果に置き換え
ExpressionInfoP expr1 = boost::any_cast<ExpressionInfoP>($1);
ExpressionInfoP expr2 = boost::any_cast<ExpressionInfoP>($3);
if (expr1->isConst() && expr2->isConst()) {
vm_int_t val = expr1->getVal<vm_int_t>() + expr2->getVal<vm_int_t>()
ExpressionInfoP expr(new ExpressionInfo(val));
expr->addGenCode(boost::lambda::bind(&Comp::genCodePUSH_I, boost::lambda::_1, val));
$$ = expr;
}
// 定数たたみこみできない場合は、それぞれの命令出力後に add 命令を出力するように
else {
ExpressionInfoP expr(new ExpressionInfo());
expr->setCast(Cast::INT);
expr->addGenCode(*expr1);
expr->addGenCode(*expr2);
expr->addGenCode(boost::lambda::bind(&Comp::genOprADD_I, boost::lambda::_1));
$$ = expr;
}
}
// 三項演算子
typedef boost::function<void (Comp* comp)> GenCode;
typedef std::vector<GenCode> GenCodeList;
struct GenCode3Op
{
GenCode3Op(ExpressionInfoP expr_cond, ExpressionInfoP expr_true, ExpressionInfoP expr_false)
{
expr_cond->gt;swapGenCode(m_gen_cond);
expr_true->gt;swapGenCode(m_gen_true);
expr_false->gt;swapGenCode(m_gen_false);
}
/*
[expr_cond]
jump_if0 L_false
[expr_true]
jump L_exit
L_false:
[expr_false]
L_exit:
*/
void operator()(Comp* comp) const
{
_genCode(comp, m_gen_cond);
vm_taddr_t taddr1 = comp->gt;genCodeJUMP_IF0();
_genCode(comp, m_gen_true);
vm_taddr_t taddr2 = comp->gt;genCodeJUMP();
comp->gt;backpatchJUMP_IF0(taddr1, comp->gt;getCodeSize());
// L_false
_genCode(comp, m_gen_false);
// L_exit
comp->gt;backpatchJUMP(taddr2, comp->gt;getCodeSize());
}
private:
GenCodeList m_gen_cond;
GenCodeList m_gen_true;
GenCodeList m_gen_false;
};
25.06 real 11.60 user 2.51 sys
275828 maximum resident set size
4008 average shared memory size
20237 average unshared data size
203 average unshared stack size
81429 page reclaims
0 page faults
0 swaps
0 block input operations
74 block output operations
0 messages sent
0 messages received
0 signals received
1121 voluntary context switches
418 involuntary context switches
27.06 real 23.48 user 4.56 sys
493700 maximum resident set size
4221 average shared memory size
87521 average unshared data size
220 average unshared stack size
151172 page reclaims
0 page faults
0 swaps
0 block input operations
42 block output operations
0 messages sent
0 messages received
0 signals received
826 voluntary context switches
670 involuntary context switches
24.05 real 21.79 user 3.14 sys
493684 maximum resident set size
4226 average shared memory size
91473 average unshared data size
225 average unshared stack size
151171 page reclaims
0 page faults
0 swaps
0 block input operations
69 block output operations
0 messages sent
0 messages received
0 signals received
832 voluntary context switches
581 involuntary context switches
31.42 real 23.95 user 8.32 sys
493700 maximum resident set size
4268 average shared memory size
109638 average unshared data size
230 average unshared stack size
151169 page reclaims
0 page faults
0 swaps
0 block input operations
67 block output operations
0 messages sent
0 messages received
0 signals received
831 voluntary context switches
602 involuntary context switches
ラベル: プログラミング
ラベル: プログラミング
issei@vmbsd[~/work/GameObj/tool/src/cscript] sysctl hw.model
hw.model: AMD Athlon(tm) 64 X2 Dual Core Processor 4200+
issei@vmbsd[~/work/GameObj/tool/src/cscript] time gmake -j2
111.693u 43.318s 1:29.89 172.4% 5034+23476k 132+1783io 127pf+0w
${INSTALL_PROGRAM} ${WRKDIR}/vmmemctl-only/vmmemctl.ko ${VMWARE_KMODDIR}ラベル: プログラミング
#! /usr/bin/perl
use strict;
use vars qw($NM_CMD %SYMBOLS);
$NM_CMD = "/usr/bin/nm -S";
my $file_sym = $ARGV[0] or die;
&read_symbol($file_sym);
my $file_inst = $ARGV[1] or die;
open(F, "$file_inst") or die "$file_inst";
my $depth = 0;
while (<F>) {
chomp;
/^([EX])0x([\da-f]+)/ or next;
my ($ex) = $1;
my ($addr) = hex($2);
$ex eq 'E' and ++$depth;
$ex eq 'X' and --$depth;
next if $ex eq 'X';
next unless exists($SYMBOLS{$addr});
print ' ' x $depth, $SYMBOLS{$addr}, "\n";
}
close(F);
sub read_symbol
{
my $file = shift;
open(P, "$NM_CMD $file |") or die "$NM_CMD $file";
while (<P>) {
next unless /^([\da-f]+)\s+([\da-f]+)\s+[tTW]\s+(\w+)$/;
my ($begin, $size, $symbol) = ($1, $2, $3);
local($_) = $symbol;
next if /^_ZNK?5boost/;
next if /^_ZN?K?S/;
next if /^_ZNK?9__gnu_cxx/;
next if /^_Z41__static_/;
next if /^_ZNK4mpl/;
next if /^__tcf_/;
next if /^_Z8__istypeim/;
next if /^_GLOBAL__/;
next if /^_Z10__maskruneim/;
next if /^_Znw/;
next if /^_Zdl/;
next if /^__cyg_/;
$SYMBOLS{hex($begin)} = $symbol;
}
close(P);
}
main
_ZN7CScript7SysDeclC1Ev
_ZN7CScript7ICtxLexC2Ev
_ZN7CScript7SysDecl4ImplC1Ev
_ZN7CScript9LexHolderC1EPNS_7ICtxLexEPSi
_ZN7CScript9LexHolder4ImplC1EPNS_7ICtxLexEPSi
_ZN11yyFlexLexerC2EPSiPSo
_ZN9FlexLexerC2Ev
yyparse
yylex
_ZN7CScript9LexHolder5yylexEv
_ZN7CScript9LexHolder4Impl5yylexEv
(以下略)
#! /usr/bin/perl
use strict;
use vars qw($NM_CMD %SYMBOLS %CALLS %TOTAL);
$NM_CMD = "/usr/bin/nm -S";
# --------------------------------------------------------------------
my $file_sym = $ARGV[0] or die;
&read_symbol($file_sym);
my $file_inst = $ARGV[1] or die;
my (@stack);
open(F, "$file_inst") or die "$file_inst";
my $depth = 0;
while () {
chomp;
/^([EX])0x([\da-f]+)/ or next;
my ($ex) = $1;
my ($addr) = hex($2);
if ($ex eq 'E') {
push(@stack, $addr);
} elsif ($ex eq 'X') {
pop(@stack);
}
next if $ex eq 'X';
next unless exists($SYMBOLS{$addr});
my $sym_callee = $SYMBOLS{$addr};
my $sym_caller = &find_caller(\@stack);
next unless $sym_caller;
# print "$sym_caller -> $sym_callee\n";
++$CALLS{$sym_caller}{$sym_callee};
++$TOTAL{$sym_caller};
exists($TOTAL{$sym_callee}) or $TOTAL{$sym_callee} = 0;
# print ' ' x $#stack, $sym_callee, "\n";
}
close(F);
# --------------------------------------------------------------------
print "digraph G \{\n\n";
# vertecies
foreach (keys %TOTAL) {
my $caller = $_;
if ($TOTAL{$caller} > 0) {
print " \"$caller\" [shape=rectangle];\n";
} else {
print " \"$caller\" [shape=ellipse];\n";
}
}
# edges
foreach (keys %CALLS) {
my $caller = $_;
foreach (keys %{$CALLS{$caller}}) {
my $callee = $_;
my $count = $CALLS{$caller}{$callee};
print " \"$caller\" -> \"$callee\" [label=\"$count calls\" fontsize=\"10\"];\n";
}
}
print "}\n";
# --------------------------------------------------------------------
sub read_symbol
{
my $file = shift;
open(P, "$NM_CMD $file |") or die "$NM_CMD $file";
while () {
next unless /^([\da-f]+)\s+([\da-f]+)\s+[tTW]\s+(\w+)$/;
my ($begin, $size, $symbol) = ($1, $2, $3);
local($_) = $symbol;
next if /^_ZNK?5boost/;
next if /^_ZN?K?S/;
next if /^_ZNK?9__gnu_cxx/;
next if /^_Z41__static_/;
next if /^_ZNK4mpl/;
next if /^__tcf_/;
next if /^_Z8__istypeim/;
next if /^_GLOBAL__/;
next if /^_Z10__maskruneim/;
next if /^_Znw/;
next if /^_Zdl/;
next if /^__cyg_/;
$SYMBOLS{hex($begin)} = $symbol;
}
close(P);
}
sub find_caller
{
my $stack = shift;
my $depth = scalar(@$stack);
local($_) = $depth - 2;
for (; $_ >= 0; --$_) {
my $addr = $stack->[$_];
return $SYMBOLS{$addr} if $SYMBOLS{$addr};
}
}
% ./code/callgraph.pl cscrc cscrc.sym | c++filt > cscrc.dot
ラベル: プログラミング
本というのはどこか本を作る会社が作るわけで、どうやって そういう所とそういう話になるんだろう、とかいうあたりがまるで想像できません。大きな会社なら出版社とも付き合いがあると思いますから、その編集さん通して企画に適した分野の編集さん紹介してもらえると思います。
ラベル: プログラミング
ラベル: プログラミング
各分野をつまみ食いさせて興味を持たせるきっかけにしつつも、最悪そこに書いてあることだけでもとりあえずゲームが作れる、というぐらいの本があると便利な気がするのです。
ラベル: プログラミング
ゲームプログラミングについて各分野で60点を取れるくらいの 感じの本が私の知る限りない。幾何、ライブラリ設計、アプリケーション設計、 通信、素材管理、ツール、AI、モーション、最適化、デバッグ体制、描画技術、 ハードウェアアーキテクチャ、アルゴリズム、 などなどの全分野についてそれなりな知識を得られるような本。一冊ではなく、既存の書籍をワンセットにしてくれということなら簡単にリストを作れそうです。ゲームプログラミング固有の知識分野は、ほとんど無いと思いますので。
ラベル: プログラミング
struct _Rb_tree_node_base
{
typedef _Rb_tree_node_base* _Base_ptr;
typedef const _Rb_tree_node_base* _Const_Base_ptr;
_Rb_tree_color _M_color;
_Base_ptr _M_parent;
_Base_ptr _M_left;
_Base_ptr _M_right;
// 以下略
_Rb_tree_node_base*
_Rb_tree_increment(_Rb_tree_node_base* __x)
{
if (__x->_M_right != 0)
{
__x = __x->_M_right;
while (__x->_M_left != 0)
__x = __x->_M_left;
}
else
{
_Rb_tree_node_base* __y = __x->_M_parent;
while (__x == __y->_M_right)
{
__x = __y;
__y = __y->_M_parent;
}
if (__x->_M_right != __y)
__x = __y;
}
return __x;
}
ラベル: プログラミング
void main()
{
while (sysGetTimeSec() < 0.5)
sysPass(1);
// ここで無敵状態に
while (sysGetTimeSec() < 0.7)
sysPass(1);
// ここでヒット判定1回目
while (sysGetTimeSec() < 0.9)
sysPass(1);
// ここでヒット判定2回目
while (sysGetTimeSec() < 1.0)
sysPass(1);
// ここで無敵状態解除
}
timeline player_timeline()
{
when (0.5) {
// ここで無敵状態に
}
when (0.7) {
// ここでヒット判定1回目
}
when (0.9) {
// ここでヒット判定2回目
}
when (1.0) {
// ここで無敵状態解除
}
}
void main()
{
sysThreadBeginTimeline(player_timeline); // タイムライン処理の起動
sysThreadWaitAll(); // タイムライン処理が終わるまで待つ
}
when (2.3) {
do_something;
}
// ---------------------------------------- //
void __when()
{
int run = (int)(2.3 * CSCR_FRAMES_PER_SEC);
while (run >= sysThreadGetTime())
sysPass(1);
do_something;
}
// ---------------------------------------- //
void __when()
{
int run = (int)(2.3 * CSCR_FRAMES_PER_SEC); // (*1)
loop_begin: // (*2) [taddr1]
if (!(run >= sysThreadGetTime()))
goto loop_end; // [taddr2]
sysPass(1);
goto loop_begin;
loop_end:
do_something; // (*3)
}
goto label;
label_when_1:
// 最初の when 句に対応するコード
return;
label_when_2:
// 2番目の when 句に対応するコード
return;
label_when_3:
// 3番目の when 句に対応するコード
return;
label:
// when 句を、時刻の小さい順に呼び出す(コンパイル時にソートする)
call label_when_1
call label_when_3
call label_when_2
ラベル: プログラミング
// スクリプトのエントリポイントみたいなコードを書けば同じことは実現できるが、メッセージループみたいなゴツい代物をスクリプトで書くとなると、それなりのスキルがある人間しかスクリプトを書けなくなり、せっかく処理をスクリプトに逃がした意味が半減してしまう。MSG_EMPTY, MSG_EXIT の処理をミスると悲惨なことになるし。
void main()
{
int msg;
for (;;) {
// C++ 側から処理要求を取得
msg = getMessage();
switch (msg) {
case MSG_EMPTY: // 処理要求なし
sysPass(1);
case MSG_EXIT: // 終了
sysExit();
// 以下メッセージの種類に応じて、処理を分岐
case MSG_..
// ...
}
}
}
VirtualMachine vm;問題はスクリプト側関数への番号づけと、それを VirtualMachine のインスタンスに反映させる方法をどうするか? まず思いつくのが既存のシステム関数の枠組みを利用してスクリプト側から登録させる方法、それからスクリプトをコンパイルする時点で割り込み番号と割り込み関数を解析してファイルに書き出しておき、それを VirtualMachine に読み込む際にローダーが登録してしまう方法。
// 初期化処理いろいろ省略
vm.interrupt(1);
/*
* 割り込みテスト
*/
// 割り込み処理1
interrupt testIntr1()
{
sysPrintS("intr 1\n");
}
// 割り込み処理2
interrupt testIntr2()
{
sysPrintS("intr 2\n");
}
void main()
{
// 割り込み関数登録
sysIntrRegister(1, testIntr1);
sysIntrRegister(2, testIntr2);
sysPass(1);
}
/*
* 割り込みテスト
*/
__interrupt[1] testIntr1()
{
sysPrintS("intr 1\n");
}
__interrupt[2] testIntr2()
{
sysPrintS("intr 2\n");
}
void main()
{
}
ラベル: プログラミング
We discuss seven undesirable features common to many programming languages used to teach first-time programmers, and illustrate typical pedagogical difficulties which stem from them with examples drawn from the programming languages ABC, Ada, C, C++, Eiffel, Haskell, LISP, Modula 3, Pascal, Prolog, Scheme, and Turing. We propose seven language design (or selection) principles which may reduce the incidence of such undesirable features.
ラベル: プログラミング
ラベル: プログラミング
ラベル: プログラミング
ラベル: プログラミング

ラベル: プログラミング

ラベル: プログラミング
ラベル: プログラミング
// スレッドのエントリポイント
thread thread_main(void) {}
void main()
{
// スレッド作成
// ここで他にも引数を渡せるようにするかは要検討
sysThreadCreate(thread_main);
}
ラベル: プログラミング
% od -Ax -tx1 sample/varinit.scb
000000 7f 73 63 62 00 00 00 00 01 00 00 00 00 00 00 00
000010 01 00 00 00 90 00 00 00 1a b8 c9 58 00 00 00 00
000020 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000030 02 00 00 34 0a 00 00 01 00 00 00 ff 00 00 00 20
000040 01 00 00 31 fd ff 00 51 00 00 00 21 00 00 00 02
000050 00 00 00 21 00 00 00 02 02 00 00 20 03 00 00 31
000060 00 00 00 51 01 00 00 33 08 00 00 31 01 00 00 51
000070 01 00 00 33 00 00 00 50 01 00 00 50 04 00 02 80
000080 1b 00 00 04 00 00 00 50 00 00 00 52 80 00 01 10
000090 00 00 00 31 82 00 01 10 11 00 00 03 83 00 00 10
0000a0 00 00 00 40 80 00 01 10 00 00 00 21 00 00 00 02
0000b0 02 00 00 00 20 00 00 00 8b 3e 1d a3 00 00 00 00
0000c0 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000d0 00 00 00 00 0b 00 00 00 01 00 00 00 05 00 00 00
0000e0 05 00 00 00 10 00 00 00 e3 ae 1f b4 00 00 00 00
0000f0 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000100 06 00 00 00 20 00 00 00 30 7c bb 58 00 00 00 00
000110 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000130 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000140
ラベル: プログラミング
node_var: TOK_CAST node_var_list0 ';'
{ /* ここで node_var_list0 の変数を一気にシンボルテーブルに登録 */ }
node_var_list0: TOK_ID | node_var_list0 ',' TOK_ID
node_function:アクションがなければ TOK_CAST と次の TOK_ID を読んだ時点ではシフトしておき、後で '(' が出てきたら node_function に還元、そうでなければ node_var_list0 に還元すれば良いけど、途中にアクションがあるとその時点でアクションを実行するかどうかを決定する必要があるから、判別を先延ばしにできないんだ。
TOK_CAST TOK_ID '(' node_param_list ')'
'{' node_block '}'
ラベル: プログラミング
ラベル: プログラミング