有时候我们希望能够在存档文件里保存些结构化的数据,而不只是简单的字符串或者数字。但是你会发现,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;
}