diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index ed06210396..62608b3ae0 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -219,11 +219,14 @@ void StartGame(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs, int p g_Game->StartGame(gameAttribs, ""); } -CScriptVal StartSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name) +CScriptVal StartSavedGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name) { - CSimulation2* sim = g_Game->GetSimulation2(); - JSContext* cx = sim->GetScriptInterface().GetContext(); - JSAutoRequest rq(cx); + // We need to be careful with different compartments and contexts. + // The GUI calls this function from the GUI context and expects the return value in the same context. + // The game we start from here creates another context and expects data in this context. + + JSContext* cxGui = pCxPrivate->pScriptInterface->GetContext(); + JSAutoRequest rq(cxGui); ENSURE(!g_NetServer); ENSURE(!g_NetClient); @@ -231,27 +234,33 @@ CScriptVal StartSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::w ENSURE(!g_Game); // Load the saved game data from disk - CScriptValRooted metadata; + JS::RootedValue guiContextMetadata(cxGui); std::string savedState; - Status err = SavedGames::Load(name, sim->GetScriptInterface(), metadata, savedState); + Status err = SavedGames::Load(name, *(pCxPrivate->pScriptInterface), &guiContextMetadata, savedState); if (err < 0) - return CScriptVal(); + return JS::UndefinedValue(); g_Game = new CGame(); - JS::RootedValue gameMetadata(cx, metadata.get()); + { + CSimulation2* sim = g_Game->GetSimulation2(); + JSContext* cxGame = sim->GetScriptInterface().GetContext(); + JSAutoRequest rq(cxGame); + + JS::RootedValue gameContextMetadata(cxGame, + sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), guiContextMetadata)); + JS::RootedValue gameInitAttributes(cxGame); + sim->GetScriptInterface().GetProperty(gameContextMetadata, "initAttributes", &gameInitAttributes); - JS::RootedValue gameInitAttributes(cx); - sim->GetScriptInterface().GetProperty(gameMetadata, "initAttributes", &gameInitAttributes); + int playerID; + sim->GetScriptInterface().GetProperty(gameContextMetadata, "player", playerID); - int playerID; - sim->GetScriptInterface().GetProperty(gameMetadata, "player", playerID); + // Start the game + g_Game->SetPlayerID(playerID); + g_Game->StartGame(CScriptValRooted(cxGame, gameInitAttributes), savedState); + } - // Start the game - g_Game->SetPlayerID(playerID); - g_Game->StartGame(CScriptValRooted(cx, gameInitAttributes), savedState); - - return gameMetadata.get(); + return guiContextMetadata.get(); } void SaveGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filename, std::wstring description, CScriptVal GUIMetadata) diff --git a/source/ps/SavedGame.cpp b/source/ps/SavedGame.cpp index cc9a52be47..293cb7ac59 100644 --- a/source/ps/SavedGame.cpp +++ b/source/ps/SavedGame.cpp @@ -137,8 +137,19 @@ class CGameLoader { NONCOPYABLE(CGameLoader); public: - CGameLoader(ScriptInterface& scriptInterface, CScriptValRooted* metadata, std::string* savedState) : - m_ScriptInterface(scriptInterface), m_Metadata(metadata), m_SavedState(savedState) + + /** + * @param scriptInterface the ScriptInterface used for loading metadata. + * @param[out] savedState serialized simulation state stored as string of bytes, + * loaded from simulation.dat inside the archive. + * + * Note: We use a different approach for returning the string and the metadata JS::Value. + * We use a pointer for the string to avoid copies (efficiency). We don't use this approach + * for the metadata because it would be error prone with rooting and the stack-based rooting + * types and confusing (a chain of pointers pointing to other pointers). + */ + CGameLoader(ScriptInterface& scriptInterface, std::string* savedState) : + m_ScriptInterface(scriptInterface), m_SavedState(savedState) { } @@ -149,12 +160,12 @@ public: void ReadEntry(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile) { - if (pathname == L"metadata.json" && m_Metadata) + if (pathname == L"metadata.json") { std::string buffer; buffer.resize(fileInfo.Size()); WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)buffer.data()), buffer.size())); - *m_Metadata = m_ScriptInterface.ParseJSON(buffer); + m_Metadata = m_ScriptInterface.ParseJSON(buffer); } else if (pathname == L"simulation.dat" && m_SavedState) { @@ -162,13 +173,20 @@ public: WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)m_SavedState->data()), m_SavedState->size())); } } + + JS::Value GetMetadata() + { + return m_Metadata.get(); + } + +private: ScriptInterface& m_ScriptInterface; - CScriptValRooted* m_Metadata; + CScriptValRooted m_Metadata; std::string* m_SavedState; }; -Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterface, CScriptValRooted& metadata, std::string& savedState) +Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterface, JS::MutableHandleValue metadata, std::string& savedState) { // Determine the filename to load const VfsPath basename(L"saves/" + name); @@ -185,8 +203,9 @@ Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterfa if (!archiveReader) WARN_RETURN(ERR::FAIL); - CGameLoader loader(scriptInterface, &metadata, &savedState); + CGameLoader loader(scriptInterface, &savedState); WARN_RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader)); + metadata.set(loader.GetMetadata()); return INFO::OK; } @@ -223,16 +242,15 @@ std::vector SavedGames::GetSavedGames(ScriptInterface& scriptI continue; // skip this file } - JS::RootedValue metadata(cx); - CScriptValRooted tmpMetada(cx, metadata); - CGameLoader loader(scriptInterface, &tmpMetada, NULL); + CGameLoader loader(scriptInterface, NULL); err = archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader); if (err < 0) { DEBUG_WARN_ERR(err); continue; // skip this file } - + JS::RootedValue metadata(cx, loader.GetMetadata()); + JS::RootedValue game(cx); scriptInterface.Eval("({})", &game); scriptInterface.SetProperty(game, "id", pathnames[i].Basename()); diff --git a/source/ps/SavedGame.h b/source/ps/SavedGame.h index 29f5254ee1..51d608a608 100644 --- a/source/ps/SavedGame.h +++ b/source/ps/SavedGame.h @@ -74,7 +74,7 @@ Status SavePrefix(const std::wstring& prefix, const std::wstring& description, C * loaded from simulation.dat inside the archive. * @return INFO::OK if successfully loaded, else an error Status */ -Status Load(const std::wstring& name, ScriptInterface& scriptInterface, CScriptValRooted& metadata, std::string& savedState); +Status Load(const std::wstring& name, ScriptInterface& scriptInterface, JS::MutableHandleValue metadata, std::string& savedState); /** * Get list of saved games for GUI script usage