`
RednaxelaFX
  • 浏览: 3049347 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

使用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;
}
分享到:
评论

相关推荐

    unity字典序列化工具SerializableDictionary

    在Unity游戏引擎开发中,数据序列化是一个至关重要的环节,特别是在保存和加载游戏状态、传输数据或者进行网络通信时。`SerializableDictionary`是Unity社区为解决标准字典类型(`System.Collections.Generic....

    【完整光盘24.5M】开发自己的搜索引擎-Lucene 2.0+Heritrix.zip

    《构建个人搜索引擎:深入理解Lucene 2.0与Heritrix》 在这个数字化时代,搜索引擎已经成为我们获取信息的关键...无论你是想打造个人搜索引擎,还是希望在企业级应用中集成搜索功能,这份资料都将是你宝贵的参考资料。

    InnoDB源码解析

    InnoDB是一个提供了ACID事务支持的存储引擎,它在MySQL中被广泛使用,特别是在事务性数据库系统中。InnoDB为表提供了行级锁定和外键约束的特性,并且通过MVCC(多版本并发控制)机制支持高并发读写操作。 2. InnoDB...

    常用关系型数据库架构和实现原理.docx

    - 数据字典缓存(data dictionary cache):存储最近使用的对象定义 - PL/SQL 缓冲区(PL/SQL buffer):用于存储过程、函数、打包的过程、打包的函数、对象类型定义和触发器。 - **大型池(Large Buffer)**:用于共享...

    redis的概要介绍与分析

    通过上述资源的学习,无论是Redis的新手还是有经验的开发者,都能找到适合自己的学习路径,从理论到实践,逐步掌握Redis的使用和优化技巧,充分发挥其在数据处理和存储方面的强大能力。Redis作为一个高度可扩展和多...

    面试整理.md

    **`Dictionary`的数据结构实现**: 1. **基本结构**:`Dictionary, TValue&gt;`使用哈希表作为基础数据结构。 2. **数组与桶**:创建两个数组,一个是`entries`数组用于存放键值对,另一个是`buckets`数组用于记录键的...

    Nluke索引文件查看器

    倒排索引由多个组件构成,如Segment Info文件记录了段的信息,Term Dictionary用于存储所有唯一词项及其对应的文档频率和位置信息,Posting List则保存了每个词项在哪些文档出现以及它们的位置。Nluke允许用户查看...

    超级有影响力霸气的Java面试题大全文档

    在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,系统将给出警告或退出。...

    ASP3《高级编程》(第一部分)

    包含在Windows 2000中的Active Server Pages 3.0 (ASP 3.0)是Microsoft公司推出的又一个支持Internet的功能强大的网页制作软件包,除了继续保持其适应于各种浏览器的基本特征外,与ASP 2.0相比,功能更加强大,目前...

    ASP3《高级编程》(第二部分)

    包含在Windows 2000中的Active Server Pages 3.0 (ASP 3.0)是Microsoft公司推出的又一个支持Internet的功能强大的网页制作软件包,除了继续保持其适应于各种浏览器的基本特征外,与ASP 2.0相比,功能更加强大,目前...

Global site tag (gtag.js) - Google Analytics