forked from 0ad/0ad
Rewrite the clone-object-between-JS-contexts code (for GUI<->simulation interface) to be more efficient.
Delete unnecessary unused incomplete profiler scripting support. Clean up some JSAPI code to use newer features. Display simulation script functions in the profiler. This was SVN commit r7503.
This commit is contained in:
parent
3238cd8bc0
commit
fe53bce3b1
@ -57,6 +57,7 @@ CGUI
|
||||
#include "ps/Profile.h"
|
||||
|
||||
#include "scripting/ScriptingHost.h"
|
||||
#include "scripting/JSConversions.h"
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/Globals.h"
|
||||
#include "ps/Filesystem.h"
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/XML/Xeromyces.h"
|
||||
#include "scripting/ScriptingHost.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
CGUIManager* g_GUI = NULL;
|
||||
|
@ -68,49 +68,6 @@ bool IsNewSimulation(void* UNUSED(cbdata))
|
||||
return g_UseSimulation2;
|
||||
}
|
||||
|
||||
// TODO: this should probably be moved into ScriptInterface in case anyone else wants to use it
|
||||
static jsval CloneValueBetweenContexts(JSContext* cxFrom, JSContext* cxTo, jsval val)
|
||||
{
|
||||
if (JSVAL_IS_INT(val) || JSVAL_IS_BOOLEAN(val) || JSVAL_IS_NULL(val) || JSVAL_IS_VOID(val))
|
||||
return val;
|
||||
|
||||
if (JSVAL_IS_DOUBLE(val))
|
||||
{
|
||||
jsval rval;
|
||||
if (JS_NewNumberValue(cxTo, *JSVAL_TO_DOUBLE(val), &rval))
|
||||
return rval;
|
||||
else
|
||||
return JSVAL_VOID;
|
||||
}
|
||||
|
||||
if (JSVAL_IS_STRING(val))
|
||||
{
|
||||
JSString* str = JS_NewUCStringCopyN(cxTo, JS_GetStringChars(JSVAL_TO_STRING(val)), JS_GetStringLength(JSVAL_TO_STRING(val)));
|
||||
if (str == NULL)
|
||||
return JSVAL_VOID;
|
||||
return STRING_TO_JSVAL(str);
|
||||
}
|
||||
|
||||
if (JSVAL_IS_OBJECT(val))
|
||||
{
|
||||
jsval source;
|
||||
if (!JS_CallFunctionName(cxFrom, JSVAL_TO_OBJECT(val), "toSource", 0, NULL, &source))
|
||||
return JSVAL_VOID;
|
||||
if (!JSVAL_IS_STRING(source))
|
||||
return JSVAL_VOID;
|
||||
JS_AddRoot(cxFrom, &source);
|
||||
jsval rval;
|
||||
JSBool ok = JS_EvaluateUCScript(cxTo, JS_GetGlobalObject(cxTo),
|
||||
JS_GetStringChars(JSVAL_TO_STRING(source)), JS_GetStringLength(JSVAL_TO_STRING(source)),
|
||||
"(CloneValueBetweenContexts)", 0, &rval);
|
||||
JS_RemoveRoot(cxFrom, &source);
|
||||
return ok ? rval : JSVAL_VOID;
|
||||
}
|
||||
|
||||
LOGERROR(L"CloneValueBetweenContexts: value is of unexpected type");
|
||||
return JSVAL_VOID;
|
||||
}
|
||||
|
||||
CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data)
|
||||
{
|
||||
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
|
||||
@ -128,10 +85,8 @@ CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data)
|
||||
if (g_Game && g_Game->GetLocalPlayer())
|
||||
player = g_Game->GetLocalPlayer()->GetPlayerID();
|
||||
|
||||
JSContext* cxGui = guiManager->GetScriptInterface().GetContext();
|
||||
JSContext* cxSim = sim->GetScriptInterface().GetContext();
|
||||
CScriptVal ret = gui->ScriptCall(player, name, CloneValueBetweenContexts(cxGui, cxSim, data.get()));
|
||||
return CloneValueBetweenContexts(cxSim, cxGui, ret.get());
|
||||
CScriptVal ret = gui->ScriptCall(player, name, sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), data.get()));
|
||||
return guiManager->GetScriptInterface().CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get());
|
||||
}
|
||||
|
||||
void PostNetworkCommand(void* cbdata, CScriptVal cmd)
|
||||
@ -151,7 +106,7 @@ void PostNetworkCommand(void* cbdata, CScriptVal cmd)
|
||||
if (g_Game && g_Game->GetLocalPlayer())
|
||||
player = g_Game->GetLocalPlayer()->GetPlayerID();
|
||||
|
||||
jsval cmd2 = CloneValueBetweenContexts(guiManager->GetScriptInterface().GetContext(), sim->GetScriptInterface().GetContext(), cmd.get());
|
||||
jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), cmd.get());
|
||||
|
||||
queue->PushClientCommand(player, cmd2);
|
||||
// TODO: This shouldn't call Push, it should send the message to the network layer
|
||||
|
@ -444,7 +444,6 @@ static void RegisterJavascriptInterfaces()
|
||||
|
||||
// ps
|
||||
JSI_Console::init();
|
||||
CProfileNode::ScriptingInit();
|
||||
CGameAttributes::ScriptingInit();
|
||||
CPlayerSlot::ScriptingInit();
|
||||
CPlayer::ScriptingInit();
|
||||
|
@ -581,18 +581,6 @@ bool CProfileNode::Return()
|
||||
return( recursion == 0 );
|
||||
}
|
||||
|
||||
void CProfileNode::ScriptingInit()
|
||||
{
|
||||
AddProperty( L"name", (IJSObject::GetFn)&CProfileNode::JS_GetName );
|
||||
/*
|
||||
AddReadOnlyClassProperty( L"callsTotal", &CProfileNode::calls_total );
|
||||
AddReadOnlyClassProperty( L"callsPerFrame", &CProfileNode::calls_frame_last );
|
||||
AddReadOnlyClassProperty( L"timeTotal", &CProfileNode::time_total );
|
||||
AddReadOnlyClassProperty( L"timePerFrame", &CProfileNode::time_frame_last );
|
||||
*/
|
||||
CJSObject<CProfileNode, true>::ScriptingInit( "ProfilerNode" );
|
||||
}
|
||||
|
||||
CProfileManager::CProfileManager()
|
||||
{
|
||||
root = new CProfileNode( "root", NULL );
|
||||
|
@ -24,8 +24,6 @@
|
||||
|
||||
#include <vector>
|
||||
#include "Singleton.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
|
||||
|
||||
#define PROFILE_AMORTIZE
|
||||
#define PROFILE_AMORTIZE_FRAMES 50
|
||||
@ -33,7 +31,10 @@
|
||||
class CProfileManager;
|
||||
class CProfileNodeTable;
|
||||
|
||||
class CProfileNode : public CJSObject<CProfileNode, true>
|
||||
class CStr8;
|
||||
class CStrW;
|
||||
|
||||
class CProfileNode
|
||||
{
|
||||
friend class CProfileManager;
|
||||
friend class CProfileNodeTable;
|
||||
@ -112,11 +113,6 @@ public:
|
||||
void Call();
|
||||
// Leaves the node. Returns true if the node has actually been left
|
||||
bool Return();
|
||||
|
||||
// Javascript stuff...
|
||||
|
||||
static void ScriptingInit();
|
||||
jsval JS_GetName(JSContext*) { return( ToJSVal( CStrW( name ) ) ); }
|
||||
};
|
||||
|
||||
class CProfileManager : public Singleton<CProfileManager>
|
||||
|
43
source/scriptinterface/AutoRooters.h
Normal file
43
source/scriptinterface/AutoRooters.h
Normal file
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "js/jsapi.h"
|
||||
|
||||
// Exception-safety and GC-safety wrapper for JSIdArray
|
||||
// (TODO: it'd be nicer to use the existing js::AutoIdArray but currently that
|
||||
// requires a load of semi-private SpiderMonkey headers that cause a load of compile warnings)
|
||||
class IdArrayWrapper
|
||||
{
|
||||
JSContext* m_cx;
|
||||
JSIdArray* m_ida;
|
||||
public:
|
||||
IdArrayWrapper(JSContext* cx, JSIdArray* ida) :
|
||||
m_cx(cx), m_ida(ida)
|
||||
{
|
||||
for (jsint i = 0; i < m_ida->length; ++i)
|
||||
if (!JS_AddRoot(m_cx, &m_ida->vector[i]))
|
||||
debug_warn(L"JS_AddRoot failed");
|
||||
}
|
||||
~IdArrayWrapper()
|
||||
{
|
||||
for (jsint i = 0; i < m_ida->length; ++i)
|
||||
if (!JS_RemoveRoot(m_cx, &m_ida->vector[i]))
|
||||
debug_warn(L"JS_RemoveRoot failed");
|
||||
JS_DestroyIdArray(m_cx, m_ida);
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -18,15 +18,15 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "ScriptInterface.h"
|
||||
#include "AutoRooters.h"
|
||||
|
||||
#include "lib/debug.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/utf16string.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "js/jsapi.h"
|
||||
|
||||
#include <boost/preprocessor/punctuation/comma_if.hpp>
|
||||
#include <boost/preprocessor/repetition/repeat.hpp>
|
||||
|
||||
@ -35,6 +35,16 @@
|
||||
const int RUNTIME_SIZE = 4 * 1024 * 1024; // TODO: how much memory is needed?
|
||||
const int STACK_CHUNK_SIZE = 8192;
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define ENABLE_SCRIPT_PROFILING 0
|
||||
#else
|
||||
#define ENABLE_SCRIPT_PROFILING 1
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SCRIPT_PROFILING
|
||||
#include "js/jsdbgapi.h"
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ScriptInterface_impl
|
||||
@ -102,6 +112,34 @@ JSBool print(JSContext* cx, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsva
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#if ENABLE_SCRIPT_PROFILING
|
||||
static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure)
|
||||
{
|
||||
if (before)
|
||||
g_Profiler.StartScript("script invocation");
|
||||
else
|
||||
g_Profiler.Stop();
|
||||
|
||||
return closure;
|
||||
}
|
||||
|
||||
static void* jshook_function(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure)
|
||||
{
|
||||
JSFunction* fn = JS_GetFrameFunction(cx, fp);
|
||||
if (before)
|
||||
{
|
||||
if (fn)
|
||||
g_Profiler.StartScript(JS_GetFunctionName(fn));
|
||||
else
|
||||
g_Profiler.StartScript("function invocation");
|
||||
}
|
||||
else
|
||||
g_Profiler.Stop();
|
||||
|
||||
return closure;
|
||||
}
|
||||
#endif
|
||||
|
||||
ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, JSContext* cx)
|
||||
{
|
||||
JSBool ok;
|
||||
@ -117,10 +155,17 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, JSContex
|
||||
m_rt = JS_NewRuntime(RUNTIME_SIZE);
|
||||
debug_assert(m_rt); // TODO: error handling
|
||||
|
||||
#if ENABLE_SCRIPT_PROFILING
|
||||
// Register our script and function handlers - note: docs say they don't like
|
||||
// nulls passed as closures, nor should they return nulls.
|
||||
JS_SetExecuteHook(m_rt, jshook_script, this);
|
||||
JS_SetCallHook(m_rt, jshook_function, this);
|
||||
#endif
|
||||
|
||||
m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE);
|
||||
debug_assert(m_cx);
|
||||
|
||||
// For GC debugging with SpiderMonkey 1.8+:
|
||||
// For GC debugging:
|
||||
// JS_SetGCZeal(m_cx, 2);
|
||||
|
||||
JS_SetContextPrivate(m_cx, NULL);
|
||||
@ -192,7 +237,7 @@ void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs)
|
||||
m->Register(name, fptr, (uintN)nargs);
|
||||
}
|
||||
|
||||
JSContext* ScriptInterface::GetContext()
|
||||
JSContext* ScriptInterface::GetContext() const
|
||||
{
|
||||
return m->m_cx;
|
||||
}
|
||||
@ -498,3 +543,123 @@ void* ScriptInterface::GetPrivate(JSContext* cx, JSObject* obj)
|
||||
// TODO: use JS_GetInstancePrivate
|
||||
return JS_GetPrivate(cx, obj);
|
||||
}
|
||||
|
||||
|
||||
class ValueCloner
|
||||
{
|
||||
public:
|
||||
ValueCloner(JSContext* cxFrom, JSContext* cxTo) : cxFrom(cxFrom), cxTo(cxTo)
|
||||
{
|
||||
}
|
||||
|
||||
// Return the cloned object (or an already-computed object if we've cloned val before)
|
||||
jsval GetOrClone(jsval val)
|
||||
{
|
||||
if (!JSVAL_IS_GCTHING(val) || JSVAL_IS_NULL(val))
|
||||
return val;
|
||||
|
||||
std::map<jsval, jsval>::iterator it = m_Mapping.find(val);
|
||||
if (it != m_Mapping.end())
|
||||
return it->second;
|
||||
|
||||
m_ValuesOld.push_back(CScriptValRooted(cxFrom, val)); // root it so our mapping doesn't get invalidated
|
||||
|
||||
return Clone(val);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
#define CLONE_REQUIRE(expr, msg) if (!(expr)) { debug_warn(L"Internal error in CloneValueFromOtherContext: " msg); return JSVAL_VOID; }
|
||||
|
||||
// Clone a new value (and root it and add it to the mapping)
|
||||
jsval Clone(jsval val)
|
||||
{
|
||||
if (JSVAL_IS_DOUBLE(val))
|
||||
{
|
||||
jsval rval;
|
||||
CLONE_REQUIRE(JS_NewNumberValue(cxTo, *JSVAL_TO_DOUBLE(val), &rval), L"JS_NewNumberValue");
|
||||
m_Mapping[val] = rval;
|
||||
m_ValuesNew.push_back(CScriptValRooted(cxTo, rval));
|
||||
return rval;
|
||||
}
|
||||
|
||||
if (JSVAL_IS_STRING(val))
|
||||
{
|
||||
JSString* str = JS_NewUCStringCopyN(cxTo, JS_GetStringChars(JSVAL_TO_STRING(val)), JS_GetStringLength(JSVAL_TO_STRING(val)));
|
||||
CLONE_REQUIRE(str, L"JS_NewUCStringCopyN");
|
||||
jsval rval = STRING_TO_JSVAL(str);
|
||||
m_Mapping[val] = rval;
|
||||
m_ValuesNew.push_back(CScriptValRooted(cxTo, rval));
|
||||
return rval;
|
||||
}
|
||||
|
||||
debug_assert(JSVAL_IS_OBJECT(val));
|
||||
|
||||
JSObject* newObj;
|
||||
if (JS_IsArrayObject(cxFrom, JSVAL_TO_OBJECT(val)))
|
||||
{
|
||||
jsuint length;
|
||||
CLONE_REQUIRE(JS_GetArrayLength(cxFrom, JSVAL_TO_OBJECT(val), &length), L"JS_GetArrayLength");
|
||||
newObj = JS_NewArrayObject(cxTo, length, NULL);
|
||||
CLONE_REQUIRE(newObj, L"JS_NewArrayObject");
|
||||
}
|
||||
else
|
||||
{
|
||||
newObj = JS_NewObject(cxTo, NULL, NULL, NULL);
|
||||
CLONE_REQUIRE(newObj, L"JS_NewObject");
|
||||
}
|
||||
|
||||
m_Mapping[val] = OBJECT_TO_JSVAL(newObj);
|
||||
m_ValuesNew.push_back(CScriptValRooted(cxTo, OBJECT_TO_JSVAL(newObj)));
|
||||
|
||||
JSIdArray* ida = JS_Enumerate(cxFrom, JSVAL_TO_OBJECT(val));
|
||||
CLONE_REQUIRE(ida, L"JS_Enumerate");
|
||||
|
||||
IdArrayWrapper idaWrapper(cxFrom, ida);
|
||||
|
||||
for (jsint i = 0; i < ida->length; ++i)
|
||||
{
|
||||
jsid id = ida->vector[i];
|
||||
jsval idval, propval;
|
||||
CLONE_REQUIRE(JS_IdToValue(cxFrom, id, &idval), L"JS_IdToValue");
|
||||
CLONE_REQUIRE(JS_GetPropertyById(cxFrom, JSVAL_TO_OBJECT(val), id, &propval), L"JS_GetPropertyById");
|
||||
jsval newPropval = GetOrClone(propval);
|
||||
|
||||
if (JSVAL_IS_INT(idval))
|
||||
{
|
||||
// int jsids are portable across runtimes
|
||||
CLONE_REQUIRE(JS_SetPropertyById(cxTo, newObj, id, &newPropval), L"JS_SetPropertyById");
|
||||
}
|
||||
else if (JSVAL_IS_STRING(idval))
|
||||
{
|
||||
// string jsids are runtime-specific, so we need to copy the string content
|
||||
JSString* idstr = JS_ValueToString(cxFrom, idval);
|
||||
CLONE_REQUIRE(idstr, L"JS_ValueToString (id)");
|
||||
CLONE_REQUIRE(JS_SetUCProperty(cxTo, newObj, JS_GetStringChars(idstr), JS_GetStringLength(idstr), &newPropval), L"JS_SetUCProperty");
|
||||
}
|
||||
else
|
||||
{
|
||||
// this apparently could be an XML object; ignore it
|
||||
}
|
||||
}
|
||||
|
||||
return OBJECT_TO_JSVAL(newObj);
|
||||
}
|
||||
|
||||
JSContext* cxFrom;
|
||||
JSContext* cxTo;
|
||||
std::map<jsval, jsval> m_Mapping;
|
||||
std::deque<CScriptValRooted> m_ValuesOld;
|
||||
std::deque<CScriptValRooted> m_ValuesNew;
|
||||
};
|
||||
|
||||
jsval ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, jsval val)
|
||||
{
|
||||
PROFILE("CloneValueFromOtherContext");
|
||||
|
||||
JSContext* cxTo = GetContext();
|
||||
JSContext* cxFrom = otherContext.GetContext();
|
||||
|
||||
ValueCloner cloner(cxFrom, cxTo);
|
||||
return cloner.GetOrClone(val);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public:
|
||||
void SetCallbackData(void* cbdata);
|
||||
static void* GetCallbackData(JSContext* cx);
|
||||
|
||||
JSContext* GetContext();
|
||||
JSContext* GetContext() const;
|
||||
|
||||
/**
|
||||
* Call a constructor function, roughly equivalent to JS "new ctor".
|
||||
@ -155,6 +155,14 @@ public:
|
||||
*/
|
||||
bool LoadScript(const std::wstring& filename, const std::wstring& code);
|
||||
|
||||
/**
|
||||
* Construct a new value (usable in this ScriptInterface's context) by cloning
|
||||
* a value from a different context.
|
||||
* Complex values (functions, XML, etc) won't be cloned correctly, but basic
|
||||
* types and cyclic references should be fine.
|
||||
*/
|
||||
jsval CloneValueFromOtherContext(const ScriptInterface& otherContext, jsval val);
|
||||
|
||||
/**
|
||||
* Convert a jsval to a C++ type. (This might trigger GC.)
|
||||
*/
|
||||
|
@ -56,4 +56,51 @@ public:
|
||||
TS_ASSERT(!script.LoadScript(L"test.js", L"with(1){}"));
|
||||
TS_ASSERT_WSTR_CONTAINS(logger.GetOutput(), L"JavaScript error: test.js line 1\nSyntaxError: strict mode code may not contain \'with\' statements");
|
||||
}
|
||||
|
||||
void test_clone_basic()
|
||||
{
|
||||
ScriptInterface script1("Test");
|
||||
ScriptInterface script2("Test");
|
||||
|
||||
CScriptVal obj1;
|
||||
TS_ASSERT(script1.Eval("({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})", obj1));
|
||||
|
||||
CScriptVal obj2 = script2.CloneValueFromOtherContext(script1, obj1.get());
|
||||
|
||||
std::string source;
|
||||
TS_ASSERT(script2.CallFunction(obj2.get(), "toSource", source));
|
||||
TS_ASSERT_STR_EQUALS(source, "({x:123, y:[1, 1.5, \"2\", \"test\", (void 0), null, true, false]})");
|
||||
}
|
||||
|
||||
void test_clone_getters()
|
||||
{
|
||||
// The tests should be run with JS_SetGCZeal so this can try to find GC bugs
|
||||
|
||||
ScriptInterface script1("Test");
|
||||
ScriptInterface script2("Test");
|
||||
|
||||
CScriptVal obj1;
|
||||
TS_ASSERT(script1.Eval("var s = '?'; var v = ({get x() { return 123 }, 'y': {'w':{get z() { delete v.y; delete v.n; v = null; s += s; return 4 }}}, 'n': 100}); v", obj1));
|
||||
|
||||
CScriptVal obj2 = script2.CloneValueFromOtherContext(script1, obj1.get());
|
||||
|
||||
std::string source;
|
||||
TS_ASSERT(script2.CallFunction(obj2.get(), "toSource", source));
|
||||
TS_ASSERT_STR_EQUALS(source, "({x:123, y:{w:{z:4}}, n:(void 0)})");
|
||||
}
|
||||
|
||||
void test_clone_cyclic()
|
||||
{
|
||||
ScriptInterface script1("Test");
|
||||
ScriptInterface script2("Test");
|
||||
|
||||
CScriptVal obj1;
|
||||
TS_ASSERT(script1.Eval("var x = []; x[0] = x; ({'a': x, 'b': x})", obj1));
|
||||
|
||||
CScriptVal obj2 = script2.CloneValueFromOtherContext(script1, obj1.get());
|
||||
|
||||
std::string source;
|
||||
TS_ASSERT(script2.CallFunction(obj2.get(), "toSource", source));
|
||||
TS_ASSERT_STR_EQUALS(source, "({a:#1=[#1#], b:#1#})");
|
||||
}
|
||||
};
|
||||
|
@ -27,7 +27,7 @@
|
||||
#include "ps/utf16string.h"
|
||||
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
#include "js/jsapi.h"
|
||||
#include "scriptinterface/AutoRooters.h"
|
||||
|
||||
CBinarySerializer::CBinarySerializer(ScriptInterface& scriptInterface) :
|
||||
m_ScriptInterface(scriptInterface)
|
||||
@ -104,47 +104,6 @@ void CBinarySerializer::PutScriptVal(const char* UNUSED(name), jsval value)
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Exception-safety and GC-safety wrapper for JSIdArray
|
||||
class IdArrayWrapper
|
||||
{
|
||||
JSContext* m_cx;
|
||||
JSIdArray* m_ida;
|
||||
public:
|
||||
IdArrayWrapper(JSContext* cx, JSIdArray* ida) :
|
||||
m_cx(cx), m_ida(ida)
|
||||
{
|
||||
for (jsint i = 0; i < m_ida->length; ++i)
|
||||
if (!JS_AddRoot(m_cx, &m_ida->vector[i]))
|
||||
throw PSERROR_Serialize_ScriptError("JS_AddRoot failed");
|
||||
}
|
||||
~IdArrayWrapper()
|
||||
{
|
||||
for (jsint i = 0; i < m_ida->length; ++i)
|
||||
if (!JS_RemoveRoot(m_cx, &m_ida->vector[i]))
|
||||
throw PSERROR_Serialize_ScriptError("JS_RemoveRoot failed");
|
||||
JS_DestroyIdArray(m_cx, m_ida);
|
||||
}
|
||||
};
|
||||
|
||||
class RootWrapper
|
||||
{
|
||||
JSContext* m_cx;
|
||||
void* m_obj;
|
||||
public:
|
||||
// obj must be a JSObject** or JSString** or jsval* etc
|
||||
RootWrapper(JSContext* cx, void* obj) :
|
||||
m_cx(cx), m_obj(obj)
|
||||
{
|
||||
if (!JS_AddRoot(m_cx, m_obj))
|
||||
throw PSERROR_Serialize_ScriptError("JS_AddRoot failed");
|
||||
}
|
||||
~RootWrapper()
|
||||
{
|
||||
if (!JS_RemoveRoot(m_cx, m_obj))
|
||||
throw PSERROR_Serialize_ScriptError("JS_RemoveRoot failed");
|
||||
}
|
||||
};
|
||||
|
||||
void CBinarySerializer::HandleScriptVal(jsval val)
|
||||
{
|
||||
JSContext* cx = m_ScriptInterface.GetContext();
|
||||
@ -216,40 +175,26 @@ void CBinarySerializer::HandleScriptVal(jsval val)
|
||||
for (jsint i = 0; i < ida->length; ++i)
|
||||
{
|
||||
jsval idval, propval;
|
||||
uintN attrs;
|
||||
JSBool found;
|
||||
|
||||
// Find the attribute name
|
||||
// (TODO: just use JS_GetPropertyById if we ever upgrade to Spidermonkey 1.8.1)
|
||||
|
||||
if (!JS_IdToValue(cx, ida->vector[i], &idval))
|
||||
{
|
||||
LOGERROR(L"JS_IdToValue failed");
|
||||
throw PSERROR_Serialize_ScriptError();
|
||||
}
|
||||
|
||||
JSString* idstr = JS_ValueToString(cx, idval);
|
||||
if (!idstr)
|
||||
{
|
||||
LOGERROR(L"JS_ValueToString failed");
|
||||
throw PSERROR_Serialize_ScriptError();
|
||||
}
|
||||
RootWrapper idstrWrapper(cx, &idstr);
|
||||
|
||||
if (!JS_GetUCPropertyAttributes(cx, obj, JS_GetStringChars(idstr), JS_GetStringLength(idstr), &attrs, &found))
|
||||
throw PSERROR_Serialize_ScriptError("JS_GetUCPropertyAttributes failed");
|
||||
if (!found)
|
||||
throw PSERROR_Serialize_ScriptError("JS_GetUCPropertyAttributes didn't find enumerated property");
|
||||
|
||||
if (attrs & JSPROP_GETTER)
|
||||
// Forbid getters, because they will be weird and not serialise properly
|
||||
JSPropertyDescriptor descr;
|
||||
if (!JS_GetPropertyDescriptorById(cx, obj, ida->vector[i], JSRESOLVE_QUALIFIED, &descr))
|
||||
throw PSERROR_Serialize_ScriptError("JS_GetPropertyDescriptorById failed");
|
||||
if (descr.attrs & JSPROP_GETTER)
|
||||
throw PSERROR_Serialize_ScriptError("Cannot serialize property getters");
|
||||
|
||||
// Get the property name as a string
|
||||
if (!JS_IdToValue(cx, ida->vector[i], &idval))
|
||||
throw PSERROR_Serialize_ScriptError("JS_IdToValue failed");
|
||||
JSString* idstr = JS_ValueToString(cx, idval);
|
||||
if (!idstr)
|
||||
throw PSERROR_Serialize_ScriptError("JS_ValueToString failed");
|
||||
CScriptValRooted idstrRoot(cx, STRING_TO_JSVAL(idstr));
|
||||
|
||||
ScriptString("prop name", idstr);
|
||||
if (!JS_GetUCProperty(cx, obj, JS_GetStringChars(idstr), JS_GetStringLength(idstr), &propval))
|
||||
{
|
||||
LOGERROR(L"JS_GetUCProperty failed");
|
||||
throw PSERROR_Serialize_ScriptError();
|
||||
}
|
||||
|
||||
if (!JS_GetPropertyById(cx, obj, ida->vector[i], &propval))
|
||||
throw PSERROR_Serialize_ScriptError("JS_GetPropertyById failed");
|
||||
|
||||
HandleScriptVal(propval);
|
||||
}
|
||||
|
@ -73,26 +73,6 @@ void CStdDeserializer::FreeScriptBackrefs()
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
class RootWrapper
|
||||
{
|
||||
JSContext* m_cx;
|
||||
void* m_obj;
|
||||
public:
|
||||
// obj must be a JSObject** or JSString** or jsval* etc
|
||||
RootWrapper(JSContext* cx, void* obj) :
|
||||
m_cx(cx), m_obj(obj)
|
||||
{
|
||||
if (!JS_AddRoot(m_cx, m_obj))
|
||||
throw PSERROR_Deserialize_ScriptError("JS_AddRoot failed");
|
||||
}
|
||||
~RootWrapper()
|
||||
{
|
||||
if (!JS_RemoveRoot(m_cx, m_obj))
|
||||
throw PSERROR_Deserialize_ScriptError("JS_RemoveRoot failed");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
jsval CStdDeserializer::ReadScriptVal(JSObject* appendParent)
|
||||
{
|
||||
JSContext* cx = m_ScriptInterface.GetContext();
|
||||
@ -120,7 +100,7 @@ jsval CStdDeserializer::ReadScriptVal(JSObject* appendParent)
|
||||
|
||||
if (!obj)
|
||||
throw PSERROR_Deserialize_ScriptError();
|
||||
RootWrapper objWrapper(cx, &obj);
|
||||
CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj));
|
||||
|
||||
AddScriptBackref(obj);
|
||||
|
||||
@ -133,7 +113,7 @@ jsval CStdDeserializer::ReadScriptVal(JSObject* appendParent)
|
||||
StringUTF16(propname);
|
||||
|
||||
jsval propval = ReadScriptVal(NULL);
|
||||
RootWrapper propvalWrapper(cx, &propval);
|
||||
CScriptValRooted propvalRoot(cx, propval);
|
||||
|
||||
if (!JS_SetUCProperty(cx, obj, (const jschar*)propname.data(), propname.length(), &propval))
|
||||
throw PSERROR_Deserialize_ScriptError();
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "ps/Filesystem.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/GameSetup/Paths.h"
|
||||
#include "scripting/ScriptingHost.h"
|
||||
|
||||
using namespace AtlasMessage;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user