Index: binaries/data/mods/_test.sim/simulation/components/test-modding1.js
===================================================================
--- /dev/null
+++ binaries/data/mods/_test.sim/simulation/components/test-modding1.js
@@ -0,0 +1,13 @@
+function Modding() {}
+
+Modding.prototype.Schema = "";
+
+Modding.prototype.Init = function() {
+ this.x = +this.template.x;
+};
+
+Modding.prototype.GetX = function() {
+ return this.x;
+};
+
+Engine.RegisterComponentType(IID_Test1, "Modding", Modding);
Index: binaries/data/mods/_test.sim/simulation/components/test-modding2.js
===================================================================
--- /dev/null
+++ binaries/data/mods/_test.sim/simulation/components/test-modding2.js
@@ -0,0 +1,5 @@
+Modding.prototype.GetX = function() {
+ return this.x*10;
+};
+
+Engine.ReRegisterComponentType(IID_Test1, "Modding", Modding);
Index: source/ps/GameSetup/GameSetup.cpp
===================================================================
--- source/ps/GameSetup/GameSetup.cpp
+++ source/ps/GameSetup/GameSetup.cpp
@@ -183,8 +183,8 @@
// display progress / description in loading screen
void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task)
{
- g_GUI->GetActiveGUI()->GetScriptInterface()->SetGlobal("g_Progress", percent, true, false, true);
- g_GUI->GetActiveGUI()->GetScriptInterface()->SetGlobal("g_LoadDescription", pending_task, true, false, true);
+ g_GUI->GetActiveGUI()->GetScriptInterface()->SetGlobal("g_Progress", percent, false, true);
+ g_GUI->GetActiveGUI()->GetScriptInterface()->SetGlobal("g_LoadDescription", pending_task, false, true);
g_GUI->GetActiveGUI()->SendEventToAll("progress");
}
Index: source/scriptinterface/ScriptInterface.h
===================================================================
--- source/scriptinterface/ScriptInterface.h
+++ source/scriptinterface/ScriptInterface.h
@@ -138,11 +138,12 @@
/**
* Set the named property on the global object.
- * If @p replace is true, an existing property will be overwritten; otherwise attempts
- * to set an already-defined value will fail.
+ * Optionally makes it {!ReadOnly, !DontDelete, DontEnum}.
+ * If @p hotloadable is true and @p constant is true, we will not make it DontDelete.
+ * This will allow us to override its value during hotload (i.e. when @p currentlyHotloading is true).
*/
template
- bool SetGlobal(const char* name, const T& value, bool replace = false, bool constant = true, bool enumerate = true);
+ bool SetGlobal(const char* name, const T& value, bool constant = true, bool enumerate = true, bool hotloadable = false, bool currentlyHotloading = false);
/**
* Set the named property on the given object.
@@ -362,7 +363,7 @@
bool CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const;
bool Eval_(const char* code, JS::MutableHandleValue ret) const;
bool Eval_(const wchar_t* code, JS::MutableHandleValue ret) const;
- bool SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate);
+ bool SetGlobal_(const char* name, JS::HandleValue value, bool constant, bool enumerate, bool hotloadable, bool currentlyHotloading);
bool SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool readonly, bool enumerate) const;
bool SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool readonly, bool enumerate) const;
bool SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool readonly, bool enumerate) const;
@@ -488,12 +489,12 @@
}
template
-bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace, bool constant, bool enumerate)
+bool ScriptInterface::SetGlobal(const char* name, const T& value, bool constant, bool enumerate, bool hotloadable, bool currentlyHotloading)
{
JSAutoRequest rq(GetContext());
JS::RootedValue val(GetContext());
AssignOrToJSVal(GetContext(), &val, value);
- return SetGlobal_(name, val, replace, constant, enumerate);
+ return SetGlobal_(name, val, constant, enumerate, hotloadable, currentlyHotloading);
}
template
Index: source/scriptinterface/ScriptInterface.cpp
===================================================================
--- source/scriptinterface/ScriptInterface.cpp
+++ source/scriptinterface/ScriptInterface.cpp
@@ -602,25 +602,47 @@
return JS::ObjectValue(*JS::CurrentGlobalOrNull(m->m_cx));
}
-bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate)
+bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool constant, bool enumerate, bool hotloadable, bool currentlyHotloading)
{
JSAutoRequest rq(m->m_cx);
JS::RootedObject global(m->m_cx, m->m_glob);
- if (!replace)
+
+ bool found;
+ if (!JS_HasProperty(m->m_cx, global, name, &found))
+ return false;
+ if (found)
{
- bool found;
- if (!JS_HasProperty(m->m_cx, global, name, &found))
+ JS::Rooted desc(m->m_cx);
+ if (!JS_GetOwnPropertyDescriptor(m->m_cx, global, name, &desc))
return false;
- if (found)
+
+ if (desc.isReadonly())
{
- JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name);
- return false;
+ if (!currentlyHotloading)
+ {
+ JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name);
+ return false;
+ }
+
+ if (desc.isPermanent())
+ {
+ JS_ReportError(m->m_cx, "The global \"%s\" cannot be modified during hotload. Please restart the engine.", name);
+ return false;
+ }
+
+ LOGMESSAGE("Hotloading new value for global \"%s\".", name);
+ ENSURE(JS_DeleteProperty(m->m_cx, global, name));
}
}
uint attrs = 0;
if (constant)
- attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
+ {
+ if (hotloadable)
+ attrs |= JSPROP_READONLY;
+ else
+ attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
+ }
if (enumerate)
attrs |= JSPROP_ENUMERATE;
Index: source/scriptinterface/tests/test_ScriptInterface.h
===================================================================
--- source/scriptinterface/tests/test_ScriptInterface.h
+++ source/scriptinterface/tests/test_ScriptInterface.h
@@ -254,4 +254,27 @@
TS_ASSERT(script.ParseJSON(stringified, &val));
TS_ASSERT_STR_EQUALS(script.ToString(&val), "({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})");
}
+
+ // This function tests a common way to mod functions, by crating a wrapper that
+ // extends the functionality and is then assigned to the name of the function.
+ void test_function_override()
+ {
+ ScriptInterface script("Test", "Test", g_ScriptRuntime);
+ JSContext* cx = script.GetContext();
+ JSAutoRequest rq(cx);
+
+ TS_ASSERT(script.Eval(
+ "function f() { return 1; }"
+ "f = (function (originalFunction) {"
+ "return function () { return originalFunction() + 1; }"
+ "})(f);"
+ ));
+
+ JS::RootedValue out(cx);
+ TS_ASSERT(script.Eval("f()", &out));
+
+ int outNbr = 0;
+ ScriptInterface::FromJSVal(cx, out, outNbr);
+ TS_ASSERT_EQUALS(2, outNbr);
+ }
};
Index: source/simulation2/system/ComponentManager.cpp
===================================================================
--- source/simulation2/system/ComponentManager.cpp
+++ source/simulation2/system/ComponentManager.cpp
@@ -145,6 +145,8 @@
return false;
std::string content = file.DecodeUTF8(); // assume it's UTF-8
bool ok = m_ScriptInterface.LoadScript(filename, content);
+
+ m_CurrentlyHotloading = false;
return ok;
}
@@ -322,14 +324,14 @@
{
CComponentManager* componentManager = static_cast (pCxPrivate->pCBData);
componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, false);
- componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading);
+ componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, true, true, true, componentManager->m_CurrentlyHotloading);
}
void CComponentManager::Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor)
{
CComponentManager* componentManager = static_cast (pCxPrivate->pCBData);
componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, true);
- componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading);
+ componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, true, true, true, componentManager->m_CurrentlyHotloading);
}
void CComponentManager::Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor)
@@ -387,10 +389,7 @@
void CComponentManager::Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name, JS::HandleValue value)
{
CComponentManager* componentManager = static_cast (pCxPrivate->pCBData);
-
- // Set the value, and accept duplicates only if hotloading (otherwise it's an error,
- // in order to detect accidental duplicate definitions of globals)
- componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading);
+ componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, true, true, true, componentManager->m_CurrentlyHotloading);
}
IComponent* CComponentManager::Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid)
Index: source/simulation2/tests/test_ComponentManager.h
===================================================================
--- source/simulation2/tests/test_ComponentManager.h
+++ source/simulation2/tests/test_ComponentManager.h
@@ -617,6 +617,31 @@
TS_ASSERT_EQUALS(static_cast (man.QueryInterface(ent4, IID_Test1))->GetX(), 200);
}
+ void test_script_modding()
+ {
+ CSimContext context;
+ CComponentManager man(context, g_ScriptRuntime);
+ man.LoadComponentTypes();
+
+ CParamNode testParam;
+ TS_ASSERT_EQUALS(CParamNode::LoadXMLString(testParam, "100"), PSRETURN_OK);
+
+ entity_id_t ent1 = 1, ent2 = 2;
+ CEntityHandle hnd1 = man.AllocateEntityHandle(ent1);
+ CEntityHandle hnd2 = man.AllocateEntityHandle(ent2);
+
+ TS_ASSERT(man.LoadScript(L"simulation/components/test-modding1.js"));
+
+ man.AddComponent(hnd1, man.LookupCID("Modding"), testParam);
+ TS_ASSERT_EQUALS(static_cast (man.QueryInterface(ent1, IID_Test1))->GetX(), 100);
+
+ TS_ASSERT(man.LoadScript(L"simulation/components/test-modding2.js"));
+
+ TS_ASSERT_EQUALS(static_cast (man.QueryInterface(ent1, IID_Test1))->GetX(), 1000);
+ man.AddComponent(hnd2, man.LookupCID("Modding"), testParam);
+ TS_ASSERT_EQUALS(static_cast (man.QueryInterface(ent2, IID_Test1))->GetX(), 1000);
+ }
+
void test_serialization()
{
CSimContext context;