構造体(Struct)をINIファイルに保存する

投稿者: | 2017/08/23

今回は構造体のINIファイルへの保存について。

MaxScriptでよく使われるデータの保存形式として、INIファイルがあります。
INIファイルはWindowsで古くから使われてきたファイル形式なのですが、とてもシンプルなので現在でもよく使われています。

MaxScriptは標準機能だけでも簡単にINIファイルのやり取りが出来るようになっているのですが、今回は一歩突っ込んで、構造体のデータを丸ごと保存する方法について考えてみます。

INIファイルの構造

初めに、INIファイルの構造について簡単に説明しておきます。

; Comment

[sectionName1]
valueName1=value1
valueName2=value2

[sectionName2]
valueName3=value3
コメント
; (セミコロン)から始まった行はコメントとして扱われ、スキップされます。
セクション
INIファイルは複数のセクションを持つ事ができ、更にセクション内に複数のデータを持つ事が出来ます。
一般的に、セクションの多層化には対応していません。
セクションは、[ ] (ブランケット) で囲みます。
データ
セクションに含まれるデータです。
= (イコール) で区切った左型がデータ名、右側がデータ値になります。

シンプルですね。

MaxScriptでのINIファイルの扱い

MaxScriptでは標準関数だけで一通りINIファイルを扱えるようになっています。

-- 変数 x を Test.ini ファイルに保存
setINISetting "C:/Temp/Test.ini" "General" "Value1" (x as string)

-- Test.ini ファイルからデータを読み込んで変数 x に保存
local x = getINISetting "C:/Temp/Test.ini" "General" "Value1"

ここで、General はセクション名、Value1 はデータ名になります。

ファイル C:/Temp/Test.ini は存在していなくてもいきなりデータを保存する事ができ、その場合自動的にファイルが作られます。
またデータを読み出すときもエラーは発生しませんが、「データ無し」扱いとなり undefined が返されます。セクションやデータ名が存在しなかった場合も同じです。

その他にもいくつか便利な関数が用意されていますが、今回はこの辺りは本題ではないので、詳しくはリファレンスを参照してみてください。

構造体をINIファイルに保存する

上記だけでもそれなりに便利なのですが、データが増えてくると使い勝手が悪くなってきます。
出来ればデータは構造体にまとめられており、一度にまとめて保存やら読み込みやらが出来ると便利です。

そういうわけで構造体をINIファイルに保存する方法について考えてみます。

処理の仕様

データを読み書きする前に、予め構造体は定義しておくものとします。
構造体はINIファイルのセクションと一対一で対応し、複数のセクションを読書する場合はその分だけ構造体を定義しておきます。
勿論処理をまとめる構造体を作って、その中にセクション構造体をネストしても良いのですが、今回は説明のし易さ重視でシンプルに関数のみを作成します。

関数はセーブ関数とロード関数の二つのみで、関数にファイル名、セクション名、構造体を引数で渡すと、構造体の中身を保存したり読み込んだりする仕組みです。

また、構造体は定義時にデータを初期化しておくものとします。
ロード時には現在の値(初期値)のデータ型と、ファイルのデータ型を比較し、型が異なっていた場合はその値は読み込まないものとします。
こうする事で、各メンバーに意図しない型がロードされる問題を防ぐ事が出来ます。
この仕様は、場合によっては不都合になる事もあると思いますので、実際に使う時は適宜変更して使うのが良いかと思います。

セーブ関数

-- 構造体のデータをINIファイルに保存する
fn saveSettings filePath sectName dataStruct =
(
    for propName in getPropNames dataStruct do
    (
        local prop = getProperty dataStruct propName
        local cls = classof prop

        if cls != MAXScriptFunction do
        (
            -- 復元可能な文字列形式に変換する
            local str = try case cls of
            (
                name: "#" + (prop as string)
                default: prop as string
            )
            catch ""

            setINISetting filePath sectName propName str
        )
    )
)

引数

filePath
INIファイルパス。
sectName
セクション名。
dataStruct
データ構造体。
構造体は関数定義が含まれていても可。

メモ

データの文字列への変換は、今回はname型に#(シャープ)を追加するようにしていますが(name型をstringに変換すると#が外れてしまう為)、独自の文字列形式を作成する場合はこの辺りを修正してください。

ロード関数

-- INIファイルから構造体に設定データを読み込む
fn loadSettings filePath sectName dataStruct =
(
    for propName in getPropNames dataStruct do
    (
        local prop = getProperty dataStruct propName
        local cls = classof prop

        if cls != MAXScriptFunction do
        (
            local str = getINISetting filePath sectName propName

            -- 文字列からデータを復元する
            local val = try case of
            (
                (superClassOf prop == Number): str as cls  -- 数値
                (cls == string): str  -- 文字列
                default: execute str  -- その他
            )
            catch undefined

            -- データの形式チェック
            if isKindOf val cls do
                setProperty dataStruct propName val
        )
    )
)

引数

セーブ関数と同じ。

メモ

文字列からのデータの復元は基本的にexecuteで行い、数値型のみas変換を使うようにしています。
これは、executeを使うとちょっとした表記ブレでデータ型が変わってしまい、その後の形式チェックで簡単に弾かれてしまうからです。
例えば小数点の有無 (integer or float)、”P”記号の有無 (IntPtr or integer)などなど。

数値型は基本的に安全に変換出来る為、executeよりas変換の方が柔軟性は高いと思います。

文字列型に関しては、単に意味が無いのと、へたにexecuteすると例外が発生してしまう為です。

実際の使用

struct GeneralData
(
    public intVal = 200,
    public floatVal = 1.50,
    public strVal = "ABCDEFG",
    public colorVal = color 255 200 150,
    public aryVal = #(123, 2.83, "Foo", #axs),

    public fn theFunction =
    (
        print "This is dummy function."
    )
)

gd = GeneralData()
fpath = @"C:/Temp/Test.ini"

saveSettings fpath "General" gd  -- データを保存
-- or
loadSettings fpath "General" gd  -- データを読込

print gd

保存されたデータは以下のようになります。

[General]
intVal=200
colorVal=(color 255 200 150)
strVal=ABCDEFG
floatVal=1.5
aryVal=#(123, 2.83, "Foo", #axs)

勿論読み込みもうまくいきます。
executeを使っているおかげでcolor型や配列も読み込めるのは有りがたいですね。

また、関数オブジェクト(上の例ではtheFunction関数)は弾くようにしているので、関数混じりの構造体でも問題無く処理する事が出来ます。

最後に

今回は構造体をINIファイルに保存する方法を考えてみましたが、機会があればXMLなんかもやってみたいです。
INIファイルと違って階層構造も保存出来るので、うまくいけば、ほぼ完全な状態で構造体の保存が出来るのではないでしょうか。

コメントを残す

メールアドレスが公開されることはありません。