使用Dictionary保存数据的功能在引擎里的内部实现

有时候我们希望能够在存档文件里保存些结构化的数据,而不只是简单的字符串或者数字。但是你会发现,f、sf里只能保存数字、字符串、数组或者字典,却不能保存一般的Object。为什么会这样的呢?

从Initialize.tjs可以看到,f、sf分别是kag.flags、kag.sflags的别名。它们本身都是字典(或者叫关联数组)。于是我们可以对关联数组做个测试,看看问题是不是出在它上面:
class TestSave {
    var value = 0;
    
    function TestSave(initVal) {
        value = initVal;
    }
    
    property Value {
        getter() { return value; }
        setter(newValue) { value = newValue; }
    }
}
var vfx = %[ "cool" => new TestSave(1) ];
(Dictionary.saveStruct incontextof vfx)("./test.txt", "");

执行这段代码后,可以看到test.txt里的内容是:
%[
 "cool" => null /* (object) "(object 0x01702668:0x01702668)" */
]

也就是说虽然要保存的对象本身并不是null(如果是null的话,保存时不会写出object地址的注释),但是却被保存为了null。所以很明显,向f或者sf写入对象的话肯定也是一样的结果。

但是到底系统是怎么实现这个功能导致了这个结果的呢?

============================================================================

先看看KAG是如何读取和保存系统变量的:

MainWindow.tjs/1007 写道
function loadSystemVariables()
{
    // システム変数の読み込み
    try
    {
        var fn = saveDataLocation + "/" + dataName +
            "sc.ksd";
        if(Storages.isExistentStorage(fn))
        {
            scflags = Scripts.evalStorage(fn);
            scflags = %[] if scflags === void;
        }
        else
        {
            scflags = %[];
        }

        var fn = saveDataLocation + "/" + dataName +
            "su.ksd";
        if(Storages.isExistentStorage(fn))
        {
            sflags = Scripts.evalStorage(fn);
            sflags = %[] if sflags === void;
        }
        else
        {
            sflags = %[];
        }
    }
    catch(e)
    {
        throw new Exception("システム変数データを読み込めないか、"
            "あるいはシステム変数データが壊れています(" + e.message + ")");
    }
}


MainWindow.tjs/1140 写道
function saveSystemVariables()
{
    // システム変数の保存
    if(!isMain) return;

    // プラグインを呼ぶ
    forEachEventHook('onSaveSystemVariables',
        function(handler, f) { handler(); } incontextof this);

    // フルスクリーン
    scflags.fullScreen = fullScreened;

    // 文字表示速度
    scflags.autoModePageWait = autoModePageWait;
    scflags.autoModeLineWait = autoModeLineWait;
    scflags.userChSpeed = userChSpeed;
    scflags.userCh2ndSpeed = userCh2ndSpeed;
    scflags.chDefaultAntialiased = chDefaultAntialiased;
    scflags.chDefaultFace = chDefaultFace;
    scflags.chNonStopToPageBreak = chNonStopToPageBreak;
    scflags.ch2ndNonStopToPageBreak = ch2ndNonStopToPageBreak;

    // ブックマーク名
    scflags.bookMarkNames = bookMarkNames; // (コピーではなくて)参照で十分
    scflags.bookMarkDates = bookMarkDates;
    scflags.bookMarkProtectedStates = bookMarkProtectedStates;

    scflags.lastSaveDataNameGlobal = lastSaveDataNameGlobal;

    // ファイルに書き込む
    if(!readOnlyMode)
    {
        var fn = saveDataLocation + "/" + dataName +
            "sc.ksd";
        (Dictionary.saveStruct incontextof scflags)(fn, saveDataMode);

        var fn = saveDataLocation + "/" + dataName +
            "su.ksd";
        (Dictionary.saveStruct incontextof sflags)(fn, saveDataMode);
    }
}


看了这两个函数应该可以理解,系统在保存系统变量(sf)时其实就是把一个字典的内容以字面量的形式写道外部文件(存档)中。在读取存档时,只要对存档里的字典字面量eval一下就能还原其内容。很直观。
对游戏变量(f)也是同理,这里就略过了。

============================================================================

然后再看看TJS2引擎是如何实现Dictionary.saveStruct()的。

tjsDictionary.cpp/59 写道
TJS_BEGIN_NATIVE_METHOD_DECL(/*func.name*/saveStruct)
{
    // Structured output for flie;
    // the content can be interpret as an expression to re-construct the object.

    TJS_GET_NATIVE_INSTANCE(/* var. name */ni, /* var. type */tTJSDictionaryNI);
    if(!ni->IsValid()) return TJS_E_INVALIDOBJECT;

    if(numparams < 1) return TJS_E_BADPARAMCOUNT;

    ttstr name(*param[0]);
    ttstr mode;
    if(numparams >= 2 && param[1]->Type() != tvtVoid) mode = *param[1];

    iTJSTextWriteStream * stream = TJSCreateTextStreamForWrite(name, mode);
    try
    {
        std::vector<iTJSDispatch2 *> stack;
        stack.push_back(objthis);
        tTJSStringAppender string;
        ni->SaveStructuredData(stack, string, TJS_W(""));
        stream->Write(ttstr(string.GetData(), string.GetLen()));
    }
    catch(...)
    {
        stream->Destruct();
        throw;
    }
    stream->Destruct();

    if(result) *result = tTJSVariant(objthis, objthis);

    return TJS_S_OK;
}
TJS_END_NATIVE_STATIC_METHOD_DECL(/*func.name*/saveStruct)


tjsDictionary.cpp/263 写道
void tTJSDictionaryNI::SaveStructuredData(std::vector<iTJSDispatch2 *> &stack,
    tTJSStringAppender & string, const ttstr &indentstr)
{
#ifdef TJS_TEXT_OUT_CRLF
    string += TJS_W("%[\r\n");
#else
    string += TJS_W("%[\n");
#endif
    ttstr indentstr2 = indentstr + TJS_W(" ");

    tSaveStructCallback callback;
    callback.Stack = &stack;
    callback.String = &string;
    callback.IndentStr = &indentstr2;
    callback.First = true;

    // in header file: tTJSCustomObject * Owner;
    Owner->EnumMembers(TJS_IGNOREPROP, &tTJSVariantClosure(&callback, NULL), Owner);

#ifdef TJS_TEXT_OUT_CRLF
    if(!callback.First) string += TJS_W("\r\n");
#else
    if(!callback.First) string += TJS_W("\n");
#endif
    string += indentstr;
    string += TJS_W("]");
}


tjsDictionary.cpp/290 写道
tjs_error TJS_INTF_METHOD tTJSDictionaryNI::tSaveStructCallback::FuncCall(
    tjs_uint32 flag, const tjs_char * membername, tjs_uint32 *hint,
    tTJSVariant *result, tjs_int numparams, tTJSVariant **param,
    iTJSDispatch2 *objthis)
{
    // called indirectly from tTJSDictionaryNI::SaveStructuredData

    if(numparams < 3) return TJS_E_BADPARAMCOUNT;

    // hidden members are not processed
    tjs_uint32 flags = (tjs_int)*param[1];
    if(flags & TJS_HIDDENMEMBER)
    {
        if(result) *result = (tjs_int)1;
        return TJS_S_OK;
    }

#ifdef TJS_TEXT_OUT_CRLF
    if(!First) *String += TJS_W(",\r\n");
#else
    if(!First) *String += TJS_W(",\n");
#endif

    First = false;

    *String += *IndentStr;

    *String += TJS_W("\"");
    *String += ttstr(*param[0]).EscapeC();
    *String += TJS_W("\" => ");

    tTJSVariantType type = param[2]->Type();
    if(type == tvtObject)
    {
        // object
        tTJSVariantClosure clo = param[2]->AsObjectClosureNoAddRef();
        tTJSArrayNI::SaveStructuredDataForObject(clo.SelectObjectNoAddRef(),
            *Stack, *String, *IndentStr);
    }
    else
    {
        *String += TJSVariantToExpressionString(*param[2]);
    }

    if(result) *result = (tjs_int)1;
    return TJS_S_OK;
}


tjsDictionary把读取数据的工作交给了tjsCustomObject(Owner)。Owner接下来会遍历自己所储存的属性,参见下面引用自tjsObject.cpp的几段代码;遍历的时候会调用一个callback,也就是上面的FuncCall()。
注意到这个FuncCall里是如何处理Object类型的数据的:它调用了tTJSArrayNI::SaveStructureDataForObject(),实现如下:

tjsArray.cpp/1003 写道
void tTJSArrayNI::SaveStructuredDataForObject(iTJSDispatch2 *dsp,
        std::vector<iTJSDispatch2 *> &stack, tTJSStringAppender &string,
        const ttstr &indentstr)
{
    // check object recursion
    std::vector<iTJSDispatch2 *>::iterator i;
    for(i = stack.begin(); i!=stack.end(); i++)
    {
        if(*i == dsp)
        {
            // object recursion detected
            string += TJS_W("null /* object recursion detected */");
            return;
        }
    }

    // determin dsp's object type
    tTJSDictionaryNI *dicni = NULL;
    tTJSArrayNI *arrayni = NULL;
    if(dsp && TJS_SUCCEEDED(dsp->NativeInstanceSupport(TJS_NIS_GETINSTANCE,
        TJSGetDictionaryClassID(), (iTJSNativeInstance**)&dicni)) )
    {
        // dictionary
        stack.push_back(dsp);
        dicni->SaveStructuredData(stack, string, indentstr);
        stack.pop_back();
    }
    else if(dsp && TJS_SUCCEEDED(dsp->NativeInstanceSupport(TJS_NIS_GETINSTANCE,
        ClassID_Array, (iTJSNativeInstance**)&arrayni)) )
    {
        // array
        stack.push_back(dsp);
        arrayni->SaveStructuredData(stack, string, indentstr);
        stack.pop_back();
    }
    else if(dsp != NULL)
    {
        // other objects
        string += TJS_W("null /* (object) \""); // stored as a null
        tTJSVariant val(dsp,dsp);
        string += ttstr(val).EscapeC();
        string += TJS_W("\" */");
    }
    else
    {
        // null
        string += TJS_W("null");
    }
}


问题就出在这里了。这个函数的逻辑只会正确保存字典、数组或者null;其它Object类型的对象都被保存为了null。

于是这是一个by design的问题了吧……sigh。真是糟糕的决定。

-------------------------------------------------------------------------------

tjsObject.cpp/1202 写道
void tTJSCustomObject::InternalEnumMembers(tjs_uint32 flags,
    tTJSVariantClosure *callback, iTJSDispatch2 *objthis)
{
    // enumlate members by calling callback.
    // note that member changes(delete or insert) through this function is not guaranteed.
    if(!callback) return;

    tTJSVariant name;
    tTJSVariant newflags;
    tTJSVariant value;
    tTJSVariant * params[3] = { &name, &newflags, &value };

    const tTJSSymbolData * lv1 = Symbols;
    const tTJSSymbolData * lv1lim = lv1 + HashSize;
    for(; lv1 < lv1lim; lv1++)
    {
        const tTJSSymbolData * d = lv1->Next;
        while(d)
        {
            const tTJSSymbolData * nextd = d->Next;

            if(d->SymFlags & TJS_SYMBOL_USING)
            {
                if(!CallEnumCallbackForData(flags, params, *callback, objthis, d)) return ;
            }
            d = nextd;
        }

        if(lv1->SymFlags & TJS_SYMBOL_USING)
        {
            if(!CallEnumCallbackForData(flags, params, *callback, objthis, lv1)) return ;
        }
    }
}


tjsObject.cpp/1177 写道
bool tTJSCustomObject::CallEnumCallbackForData(
    tjs_uint32 flags, tTJSVariant ** params,
    tTJSVariantClosure & callback, iTJSDispatch2 * objthis,
    const tTJSCustomObject::tTJSSymbolData * data)
{
    tjs_uint32 newflags = 0;
    if(data->SymFlags & TJS_SYMBOL_HIDDEN) newflags |= TJS_HIDDENMEMBER;
    if(data->SymFlags & TJS_SYMBOL_STATIC) newflags |= TJS_STATICMEMBER;

    *params[0] = data->Name;
    *params[1] = (tjs_int)newflags;

    if(!(flags & TJS_ENUM_NO_VALUE))
    {
        // get value
        if(TJS_FAILED(TJSDefaultPropGet(flags, *(tTJSVariant*)(&(data->Value)),
            params[2], objthis))) return false;
    }

    tTJSVariant res;
    if(TJS_FAILED(callback.FuncCall(NULL, NULL, NULL, &res,
        (flags & TJS_ENUM_NO_VALUE) ? 2 : 3, params, NULL))) return false;
    return (bool)(tjs_int)(res);
}


tjsObject.cpp/1342 写道
tjs_error TJSDefaultPropGet(tjs_uint32 flag, tTJSVariant &targ, tTJSVariant *result,
    iTJSDispatch2 *objthis)
{
    if(!(flag & TJS_IGNOREPROP))
    {
        // if TJS_IGNOREPROP is not specified

        // if member's type is tvtObject, call the object's PropGet with "member=NULL"
        //  ( default member invocation ). if it is succeeded, return its return value.
        // if the PropGet's return value is TJS_E_ACCESSDENYED,
        // return as an error, otherwise return the member itself.
        if(targ.Type() == tvtObject)
        {
            tTJSVariantClosure tvclosure = targ.AsObjectClosure();
            tjs_error hr = TJS_E_NOTIMPL;
            try
            {
                if(tvclosure.Object)
                {
                    hr = tvclosure.Object->PropGet(0, NULL, NULL, result,
                        TJS_SELECT_OBJTHIS(tvclosure, objthis));
                }
            }
            catch(...)
            {
                tvclosure.Release();
                throw;
            }
            tvclosure.Release();
            if(TJS_SUCCEEDED(hr)) return hr;
            if(hr != TJS_E_NOTIMPL && hr != TJS_E_INVALIDTYPE &&
                hr != TJS_E_INVALIDOBJECT)
                return hr;
        }
    }

    // return the member itself
    if(!result) return TJS_E_INVALIDPARAM;

    result->CopyRef(targ);

    return TJS_S_OK;
}

你可能感兴趣的:(游戏,F#)