放置、復帰、そして……新学期

今週のお題「卒業」


あれから1年放置してしまいました…… 何やってるんでしょうね。
まぁ実際、更新してる暇なんてなかったわけです。書くことが無かったんじゃなくて、ありすぎて整理する暇が無かったんです、というありがちな言い訳をしておきます。


とりあえず色々ありすぎた中からザックリと抜粋してみます。


前の記事のタイトル「新学期」は既に前々学期だった件について。
本当にすいませんでした!


とりあえず、こんな感じです。

  • 卒研のOS制作はポシャりました。時間的に無理でした。
  • 計算可能性理論は勉強したけど全然分かった気がしないのでテスト受けませんでした。(滝汗)
  • 他はまぁ、大体そんな感じです。


そして言及さえされなかった前学期ですが、

  • 卒研でQualcommさんと提携してMPEG2システムの多重化ストリームを色々操作する開発をしました。実はユダヤ企業なQualcommさんと仲良くなれました。わーい。
  • なんか今までうまくいってなかった信号処理や電子回路関係の試験がようやく通りました。わーい。
  • 他はまぁ、大体そんな感じです。


そして来週から授業開始する新学期ですが、

  • 泣いても笑っても最終学期になります。何とかテクニオンを卒業できそうです。(「卒業」が今週のお題らしいので)
  • 卒研で電子回路まわりのソフトウェア開発をすることになりそうです。(未定)
  • イタリア語やる予定です。
  • 卓球やる予定です。
  • 後は計算可能性理論リベンジとかコンパイラ理論とかファイルシステムとか、まだ選択科目として取ってないソフトウェア周りを攻める予定です。
  • 他はまぁ、大体そんな感じです。


そして、地味に進めていたスクリプトのプロジェクトですが、

  • wxWidgetsとLuaで開発していたLevanaですが、スマートフォンでの動作が重要視され始めたので一時凍結しています
  • 代わりにSDL-1.3とBoostとLuaをベースにした軽量高速なメディアエンジンLevの開発を始めました。
  • Levの開発開始に伴い、それをベースとしたタグベースのスクリプトエンジンKanafの開発を始めました。


ヘブライ語でLevは心(心臓)、Kanafは(単数形で)翼の意です。Google Codeで鋭意開発中です。
公式ページは、

です。
今後はLevやKanafの開発の様子や解説なんかを掲載できたらと思っていますが、何かと時間に追われているので再び放置になっても怒らないで下さい。ソフトウェア開発の方は着々と進んでいます。

新学期

新学期始まりました。全然更新時間無いけど生存報告までに。


卒業研究で独自OSをフルスクラッチで作る事になりました。
他に自然言語処理やら人工知能やら電気実験実習やらがあって、
間違いなく最もハードなセメスターで死にそうですが、
やっと本当に願った学びができそうです。卒業に向かって踏ん張ります。


あと、フランス語も勉強します。
あと、体育が必修科目で、卓球やりたかったけど何故か未経験のテニスをすることに。(←一番の不安要素)


更に数値解析やら計算可能性理論も一応受講申請してますが、
前の学期で幾つかの通らなかった単位の再履修とかで、この辺は未確定。
ていうか物理的に無理な気しかしない。


因みに、こっそりSF.netを抜けだしてGoogle Codeに乗り換え計画してて、
http://code.google.com/p/levana/
にプロジェクトページ作りましたが、上記の理由でOSとか作らなきゃいけないので、
こっちは長期停滞するかもです。

Luaでusingディレクティブを実現する2

id:Akiva:20110120#1295539117[Luaでusingディレクティブを実現する1]の続きです。


昨日のエントリーで大体前置きは書いたので、コードを貼っておきます。
これもLevライブラリに取り込む予定なので本来MITライセンスですが、
掲載分のコードはパブリックドメイン扱いで結構なので適当にコピーして使って下さい。


最新版は、
http://levana.svn.sourceforge.net/viewvc/levana/trunk/lev/util.lua
からダウンロード可能です。(こっちは一応MITライセンス)
今回はリビジョン25に日本語でコメント入れておきます。

-----------------------------------------------------------------------------
-- Name:        util.lua
-- Purpose:     便利関数の定義
-----------------------------------------------------------------------------

-- 使用方法:
--   * using() : ルックアップ設定のクリア
--   * using(...) : ルックアップ設定の追加、但し ... はテーブルのリストとする

-- 依存関係
local _G = _G

-- モジュール登録
module 'util'


-- テーブル t から value を検索し、見つけたら削除
local find_and_remove = function(t, value)
  for i,j in _G.ipairs(t) do
    if (j == value) then
      _G.table.remove(t, i)
      return
    end
  end
end


-- テーブル(配列) t を逆順にしたものを返す
local reverse = function(t, ...)
  local r = {}
  for i,val in _G.ipairs(t) do
    _G.table.insert(r, 1, val)
  end
  return r
end


-- 変数名 varname で変数値のルックアップ
local lookup = function(env, varname)
  meta = _G.getmetatable(env)
  -- 最初はグローバル変数から探す
  if meta.__owner[varname] ~= nil then
    return meta.__owner[varname]
  end
  -- 無ければリストに登録されたテーブルから順番に探す
  for i,t in _G.ipairs(meta.__lookup) do
    if t[varname] ~= nil then
      return t[varname]
    end
  end
  -- それでも無ければ一段外のスコープの環境から探す
  if (meta.__parent == meta.__owner) then
    -- 一段外のスコープとグローバル環境が同じなら探す意味が無い
    -- non sense to look up twice for the same table
    return nil
  end
  return meta.__parent[varname]
end


-- 変数名 key で 値 value をグローバル変数として代入
local substitute = function(env, key, value)
  meta = _G.getmetatable(env)
  meta.__owner[key] = value
end


-- 代替usingディレクティブ
function using(...)
  -- 最初に呼び出し元の環境と、環境のメタテーブルを取得
  local env  = _G.getfenv(2)
  local meta = _G.getmetatable(env) or {}
  -- 呼び出し元(関数)のアドレスも取得しておく
  local f = _G.setfenv(2, env)
  if (meta.__caller == f) then
    -- 既に環境のセットアップ済みなので、ルックアップ設定のみ変更する
    if #{...} == 0 then
      -- もしルックアップ指定が無い(usingの空呼び出し)なら、設定を削除する
      meta.__lookup = {}
      return env
    end
    for i,val in _G.ipairs({...}) do
      find_and_remove(meta.__lookup, val)
      _G.table.insert(meta.__lookup, 1, val)
    end
    return env
  end

  -- 環境未設定なので新しい環境テーブルとメタテーブルを用意する
  local newenv  = {}
  local newmeta = {}
  -- メタテーブルに必要な項目を設定する
  newmeta.__caller = f
  newmeta.__index = lookup
  newmeta.__lookup = reverse({...})
  newmeta.__newindex = substitute
  newmeta.__owner = meta.__owner or env
  newmeta.__parent = env
  -- 新環境とメタテーブルを登録
  _G.setmetatable(newenv, newmeta)
  _G.setfenv(2, newenv)
  -- 新環境を返す
  return newenv
end


-- usingは最初からプレフィックス無しで呼べた方が嬉しい
_G.using = using


使い方はこんな感じで、

require 'util'

t1 = {x = 'x1', y = 'y1'}
t2 = {y = 'Y2', z = 'Z2'}

function test()
  using() -- 安全のためルックアップ設定のクリア
  -- 初期化しておく
  x, y, z = nil
  print(x, y, z) -- nil nil nil

  using(t1)
  print(x, y, z) -- x1 y1 nil

  using(t2) -- using(t1, t2)としても同じ
  print(x, y, z) -- x1 Y2 Z2

  using(t1) -- using(t2, t1)としても同じ
  print(x, y, z) -- x1 y1 Z2

  x = 'XXX'
  print(x, y, z) -- XXX y1 Z2
end

test()
test()
print(x, y, z) -- XXX nil nil


実行結果から分かるのは、

  • グローバル変数が存在する場合は、グローバル変数が優先される
  • グローバル変数が存在しない場合、usingされたテーブルから変数値が引用される
  • (グローバル変数が存在しない場合で)usingされたテーブルの変数名が衝突する場合は、後にusingされた方が優先される
  • スコープ外(関数外)ではスコープ内(関数内)のusingは影響しない


ただし、環境の設定が適用される「スコープ」というのが他の言語とちょっと違うので語弊があるのですが、do 〜 end ブロック内でusingした場合は、(恐らく予想に反して)ブロック外からでもアクセスできちゃいます。ただしファイル単位ではちゃんとスコープとして環境設定できるので、外部コードをrequireとかdofileした時のusingはちゃんと内部で完結されます。


また、上記の例では関数の最初にusing()という空呼び出しをしていますが、
これをしない場合は関数の再呼び出し時にもルックアップの設定が引き継がれます。
多くの場合は必要無いのですが、例えば

function f()
  newt = {x = 'hogehoge', y = 'fugafuga', z = 'piyopiyo'}
  using(newt)
  〜 -- newtへアクセスするコード
end


とした場合、一見普通に動いてようで、関数呼び出しの度に新しいテーブルを作ってルックアップ対象に登録するので、
メモリ使用量が一方的に増加してGCがうまく機能できなくなります。
なので、

  • ルックアップ設定が関数終了後も残るのでは困る場合
  • 新しく生成されたテーブルをルックアップ設定したい場合

は、どこか(普通は冒頭)でusing()すべきで、そうでない場合もやはりusing()した方が安全です。
基本的に何度もusingされる事を前提にコーディングしているので、呼び出しコストはそんなに大きくないと思います。
解説がくどすぎる感じですが、だいたいそんな感じです。

Luaでusingディレクティブを実現する1

タイトルの通り、近代の高級言語ではだいたい備わっているであろうusingディレクティブを、Luaで実現しようって話ですが、はてな日記エントリーの id:gintenlabo:20100709#1278697770 で既にやってみた方がいらっしゃるのはLuaで実際に開発に携わってる方とかはご存知だと思います。知っての通り、Luaにはディレクティブなんて概念は無く、全てがチャンクと呼ばれるステートメントやブロックの処理単位で動作するので、特殊な機能は関数とテーブルをトリッキーに使って実現して下さいっていう感じですよね。


とにかくLuaは余計な言語仕様を排除して、ミニマリズムな仕様(構文や変数)とシンタックスシュガーを武器に、他のスクリプト言語の追随を許さない軽量さを誇っています。そこに痺れる憧れ…る?


Luaではメタテーブルという仕様で、テーブルやユーザ定義型の振る舞いを制御して、いわゆる演算子オーバーロードやプロパティに相当する機能を実現でき、環境テーブルという仕様で帯域変数へのアクセス方法などを制御して、いわゆるレキシカルスコープとか名前空間といった識別子の構造化が実現できます。


ただし、(当然ながら)局所変数の方が環境テーブルよりも名前解決の優先順位が高いので、実際に行われる名前解決は、

現在のスコープの局所変数から探す→より外側のスコープの局所変数から探す→…
(ローカル変数が見つからない場合は→)
環境変数(厳密には関連付けされた環境テーブル、デフォルトでは_G)から探す
(環境テーブルのメタテーブルに、テーブルアクセス用の設定がされている場合は→)
インデックス設定(テーブルないし関数)を通じて変数の値を得る

といった手順で行われると思っていいです。こう書くと変数アクセスの為に物凄いオーバーヘッドがかかってるように見えますが、あくまで構文上の説明であって、実際はもっとうまく名前解決されてるし、ローカル変数はチャンクの分析時点でレジスタやスタックに適切にマッピングされるので積極的にローカル変数を使いましょうって話。


usingディレクティブを実現したいってのは要するに、

function f(t)
  table.insert(t, "new value")
  
  -- ...

  table.sort(t)
end

とかいう時に、テーブル名ドットのプレフィックスを省略して、(但し関数スコープ内のみで有効な)

function f(t)
  using(table)
  insert(t, "new value")

  -- ...

  sort(t)
end

なんて出来たら記述量が大幅に減って、文脈によっては見た目も凄くスッキリします。こんな機能が是非欲しい。環境テーブルとメタテーブルをうまく連携させれば実現できそうで、要するに帯域変数参照時の名前解決部分が、

関連付けられた環境テーブルから変数名を探そうとするがメタテーブルで定義されたアクセス関数で制御される
→通常通り環境テーブルから探す→ルックアップに指定されたテーブルから順番に探す

という感じで介入される感じですね。
『通常の環境テーブルから探す』の優先順位も任意にできるんですが、この順番が一番都合が良さそうです。(そうでないとreturn文以外でスコープ外へ変数を渡したい時に面倒になる)
変数代入時は普通に環境テーブルに代入してやれば良さそうです。


gintenlaboさんもこういった方法でルックアップをうまいこと実現されてて関心したんですが、どうにも関数内でこのusing関数を呼んだ時に幾つか問題が起こるようで、一つはスコープ解決が出来てない為に、関数のスコープ外(一番外側のチャンクとか)でも名前干渉が起こってしまう。もう一つは、関数内で動的生成されたテーブルをusingで設定して、この関数が何度も呼ばれると、内部のルックアップリストが増え続けて著しい速度で解放されないメモリ量も増加します。(ガベージコレクタをフル起動させても使用中のメモリは解放しようがない)


別にバグというバグではなく、あくまで使い方の問題なので、スコープに関係無くルックアップしたい場合とか、モジュールテーブルしかルックアップしない場合は全然問題無いです。ただ僕はむしろスコープ内アクセスとか任意のテーブルをルックアップとかいう使い方をしたいので、これを解決する事を念頭に実装してみました。


100行程度のコードなんですが、前置きが無駄に長くなったので後日コード掲載と説明します。

聖地のロシア餃子

相変わらずプログラミングには関係の無い記事です。
各方面で「びゃー。ロシア餃子うまいー!」っていうコメントを書きこむと、
「何それ、うpしろ」とかいう反応が(主に姉とかから)聞こえてきたので記事にしてみます。


電気コンロが汚いのは仕様です。新しい学生寮に来た時にはこの状態で、洗っても駄目でした。
見所はそこじゃないから。




勝手にロシア餃子って呼んでるけど、日本語読めるロシア人(あるいはGoogle翻訳してくるロシア人)が見たら激怒しそうなので、ちゃんと捕捉すると正式名称はペリメニ(пельмени)という、由緒正しきスラブ諸国の伝統料理でございます。ウクライナではヴァレーヌィクィ(Вареники)とも呼ばれるみたいです。左写真は大学の売店で買ってきたウクライナ風ペリメニのチルド食品の調理図です。


「茹でもの」という意味のヴァレーヌィクの通称どおり、本来はスープの具として食するのが一般的ですが、中身が七面鳥の挽肉や玉ねぎなので、ぶっちゃけ蒸し焼きにしちゃえば焼き餃子として食えるんじゃね?と思ったのが事の発端で勝手に焼き餃子風調理にしてみました。もっとカリカリにすれば良かったかも。まぁロシア人もペリメニ焼いて食べたりするらしいので怒られないはず。ダンプリング料理は世界中にあるけど、ロシアのそれが餃子っぽいのは、伊達に中国の隣国やってねぇって話ですかね。


右写真が盛り付け図。醤油や米酢はイスラエルでも普通に買えるので、自家製ラー油を加えれば完璧です。旨かった。皮が分厚くて独特の食感だけど大丈夫だ、問題ない。

2011年のLevana開発目標

色々遅れましたが、あけましておめでとうございます。


Levanaのコーディングは着々と進んでいて、
サウンド周辺の実装やLua側の変数渡しの一工夫とか色々頑張っているんですが、
どうにもキリつけてリリースの目処がたっていません。


とりあえず今年で本ソフトウェアに関して目標とするのは、

  • GUI操作やメディア再生で基本とされる機能を全て実装
  • 仕様の試行錯誤と吟味を経て確定
  • 上記二項をもってバージョン1系統をリリース、広く公表を開始する
  • 当ソフトウェアの利用例を示すために実用レベルのサンプルアプリケーションを作成

てな感じで考えてます。
機能面ではwxPython並にフルフィーチャーだと理想的だけど、そこまでの時間は取れそうにないですねぇ。
そもそも二項目の『仕様の試行錯誤』って段階が一番時間かかってる現状だしなぁ。


サンプルアプリケーションとして今構想が立っているのが、

  • Babylonを参考に、GoldenDictやLingoesよりも使いやすい辞書ソフトを作る
  • gVimを参考に、キーバインドレベルで互換性のある独自仕様のvi風エディタ
  • LevanaによるLevana開発環境

うーん。やっぱり時間かかりそうだ。

Levanaバージョン0.2.2リリース、OpenGL APIをバインディング

なんとかリリースできました。
早朝に0.2.1をリリースしたものの、Windows特有の動作バグとかあったので、
修正しまくってバージョン0.2.2としました。
あと、OpenGLのバインディングでLinuxではうまくいってたのに、
何故かWindowsではGLプレフィックスの変数型が色々邪魔して思いの外手間かかりました。
コンパイラが悪いのか、Windows用OpenGLのヘッダファイルが悪いのか……


一応0.1系列からは大幅に機能・仕様変更したので0.2にしたんですが、
未だ不安定版だから奇数で0.3にした方が良かったんだろうか。その辺の感覚がまだ掴めてません。


ずっと開発段階でドキュメントすら書けてないので、大っぴらに宣伝できないしするつもりもないんですが、
興味ある方、一緒に人柱して下さる方は、こちらからダウンロードして試してみて下さい。


https://sourceforge.net/projects/levana/


現状で実装に着手かかってるのが、

  • アプリケーション管理
  • フレーム(ウィンドウ)の生成・破棄
  • メニュー、メニューバー
  • GUIコントロールの幾つか、サイザーによるレイアウト
  • タスクバーアイコン(システムトレイとも言う)
  • イベントハンドリング
  • アイコン
  • OpenGLバインディング


近いうちに実装予定だけど、現状全く手付かずなのが、

  • 画像のロード、ブロック転送
  • サウンド周辺
  • アーカイバ周辺
  • ネットワーク周辺
  • シリアライザ周辺
  • フォント周辺
  • 小物オブジェクトの整備


うーん。この辺の実装済むまではドキュメント書くどころじゃないなぁ……
誰か時間を下さい。


とりあえず、今回のリリースでOpenGLの生APIを叩けるようにしたので、どんな感じで使えるかを紹介しておきます。
実際のリアルタイム処理には、こっちでこさえてるクラスライブラリの機能の中で覆い込むので、
普段使いでGL APIが必要になることは少くなると思うんですが、
高度なエフェクトとか必要になってくる場合の選択肢として用意しておきます。
OpenGLの学習環境としても最適なんじゃないでしょうか。


先ずは簡単なサンプルをば。


[entry.lua]

require 'lev/std'  -- 標準機能(現状ではクラス再ラッピング等)
require 'lev/gl'   -- GL使用モード(現状ではプレフィックスが短くなるだけ)

frm = lev.frame()
draw = lev.draw(frm, 640, 480)
frm:fit()  -- フレームサイズの自動調整。
frm:show()

draw:using()  -- GLコンテキストの設定
-- ただのカラフル正方形だけどコンパイルして記録してみる
gl.newlist(1, gl.COMPILE)
gl.beginprim(gl.QUADS)
  gl.color3ub(255, 0, 0)
  gl.vertex2d(-0.5, -0.5)
  gl.color3ub(0, 255, 0)
  gl.vertex2d(-0.5, 0.5)
  gl.color3ub(255, 255, 0)
  gl.vertex2d(0.5, 0.5)
  gl.color3ub(0, 0, 255)
  gl.vertex2d(0.5, -0.5)
gl.endprim()
gl.endlist()

while frm:exists() do
  draw:using()  -- 基本的に毎回GL API使う前にコンテキスト設定をする
  gl.clearcolor(0, 0, 0, 0)
  gl.clear(gl.COLOR_BUFFER_BIT)  -- とりあえず定数は大文字にしておいた。
  gl.loadidentity()  -- 一応行列操作も大丈夫って事で
  gl.scaled(1.2, 1.5, 0)  -- 伸縮してみたり。
  gl.calllist(1)  -- リスト召喚。関係無いけど小文字のlが3つ並ぶのは見た目良くないよね。
  gl.flush()  -- 描画処理の最後にフラッシュするのはお約束。
  draw:swap()  -- ダブルバッファリングが前提の実装にしようと思ってるのでスワップ。
  -- Luaランタイムが喰ってるメモリ量表示。ステータスバーの操作もプロパティで簡単になりました。
  frm.status = string.format("Used Memory: %d KB", collectgarbage("count"))
  app:yield()
end


OpenGLのAPIは、Levanaをロード(require 'lev/std')した時点で一緒にロードされますが、
levana.glテーブル内に全て設置されるので、毎回こんなプレフィックス書くのは面倒です。
自分で

gl = levana.gl

してもいいんですが、今後LevライブラリでAPIラッピングするかも知れないので一応、

require 'lev/gl'

としておいて下さい。特に配列渡しのあるAPIはLuaが苦手な分野だったりするので。


関数名は基本的に元の名前からglプレフィックスを取り除いて全部小文字にした状態でglテーブルに放りこまれてて、
glLoadIdentityみたいな単語が連続してる場合でも、

gl.loadidentity()

となります。可読性は低いけど記述性は高い。読みにくいって怒らないで下さい。
ただ、この法則でいくとglBeginとglEndの対の場合にキーワード予約されたendが問題おこすので、代わりに、

gl.beginprim(gl.プリミティブ名)

-- ..

gl.endprim()

と書くようにしました。あと、glEnableClientStateは流石に小文字じゃ読みにくいというか関数名長すぎるので、
gl.enable_cstateとしてます。変更の可能性有り。察して下さい。Disableも同様。
定数は見ての通り、GL_プレフィックスを外したものがそのままglテーブルに入ってます。
まだよく使う関数しか放りこんでないですが、リクエストがあればすぐに対応させます。
GLU関数も同様にプレフィックス外して放りこんでく予定ですが、GLUT関数は入れない予定です。


だいたいそんな感じです。