Index: ps/trunk/source/graphics/MapGenerator.cpp
===================================================================
--- ps/trunk/source/graphics/MapGenerator.cpp (revision 25441)
+++ ps/trunk/source/graphics/MapGenerator.cpp (revision 25442)
@@ -1,429 +1,429 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "MapGenerator.h"
#include "graphics/MapIO.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/status.h"
#include "lib/timer.h"
#include "lib/file/vfs/vfs_path.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/FileIo.h"
#include "ps/Profile.h"
#include "ps/Threading.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptConversions.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/JSON.h"
#include "simulation2/helpers/MapEdgeTiles.h"
#include
#include
// TODO: Maybe this should be optimized depending on the map size.
constexpr int RMS_CONTEXT_SIZE = 96 * 1024 * 1024;
extern bool IsQuitRequested();
static bool
MapGeneratorInterruptCallback(JSContext* UNUSED(cx))
{
// This may not use SDL_IsQuitRequested(), because it runs in a thread separate to SDL, see SDL_PumpEvents
if (IsQuitRequested())
{
LOGWARNING("Quit requested!");
return false;
}
return true;
}
CMapGeneratorWorker::CMapGeneratorWorker(ScriptInterface* scriptInterface) :
m_ScriptInterface(scriptInterface)
{
// If something happens before we initialize, that's a failure
m_Progress = -1;
}
CMapGeneratorWorker::~CMapGeneratorWorker()
{
// Wait for thread to end
if (m_WorkerThread.joinable())
m_WorkerThread.join();
}
void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings)
{
std::lock_guard lock(m_WorkerMutex);
// Set progress to positive value
m_Progress = 1;
m_ScriptPath = scriptFile;
m_Settings = settings;
// Launch the worker thread
m_WorkerThread = std::thread(Threading::HandleExceptions::Wrapper, this);
}
void CMapGeneratorWorker::RunThread(CMapGeneratorWorker* self)
{
debug_SetThreadName("MapGenerator");
g_Profiler2.RegisterCurrentThread("MapGenerator");
shared_ptr mapgenContext = ScriptContext::CreateContext(RMS_CONTEXT_SIZE);
// Enable the script to be aborted
JS_AddInterruptCallback(mapgenContext->GetGeneralJSContext(), MapGeneratorInterruptCallback);
self->m_ScriptInterface = new ScriptInterface("Engine", "MapGenerator", mapgenContext);
// Run map generation scripts
if (!self->Run() || self->m_Progress > 0)
{
// Don't leave progress in an unknown state, if generator failed, set it to -1
std::lock_guard lock(self->m_WorkerMutex);
self->m_Progress = -1;
}
SAFE_DELETE(self->m_ScriptInterface);
// At this point the random map scripts are done running, so the thread has no further purpose
// and can die. The data will be stored in m_MapData already if successful, or m_Progress
// will contain an error value on failure.
}
bool CMapGeneratorWorker::Run()
{
ScriptRequest rq(m_ScriptInterface);
// Parse settings
JS::RootedValue settingsVal(rq.cx);
if (!Script::ParseJSON(rq, m_Settings, &settingsVal) && settingsVal.isUndefined())
{
LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings");
return false;
}
// Prevent unintentional modifications to the settings object by random map scripts
if (!Script::FreezeObject(rq, settingsVal, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to deepfreeze settings");
return false;
}
// Init RNG seed
u32 seed = 0;
if (!Script::HasProperty(rq, settingsVal, "Seed") ||
!Script::GetProperty(rq, settingsVal, "Seed", seed))
LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0");
InitScriptInterface(seed);
RegisterScriptFunctions_MapGenerator();
// Copy settings to global variable
JS::RootedValue global(rq.cx, rq.globalValue());
if (!Script::SetProperty(rq, global, "g_MapSettings", settingsVal, true, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings");
return false;
}
// Load RMS
LOGMESSAGE("Loading RMS '%s'", m_ScriptPath.string8());
if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath.string8());
return false;
}
return true;
}
#define REGISTER_MAPGEN_FUNC(func) \
- ScriptFunction::Register<&CMapGeneratorWorker::func, ScriptFunction::ObjectFromCBData>(rq, #func);
+ ScriptFunction::Register<&CMapGeneratorWorker::func, ScriptInterface::ObjectFromCBData>(rq, #func);
#define REGISTER_MAPGEN_FUNC_NAME(func, name) \
- ScriptFunction::Register<&CMapGeneratorWorker::func, ScriptFunction::ObjectFromCBData>(rq, name);
+ ScriptFunction::Register<&CMapGeneratorWorker::func, ScriptInterface::ObjectFromCBData>(rq, name);
void CMapGeneratorWorker::InitScriptInterface(const u32 seed)
{
m_ScriptInterface->SetCallbackData(static_cast(this));
m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG);
m_MapGenRNG.seed(seed);
// VFS
JSI_VFS::RegisterScriptFunctions_Maps(*m_ScriptInterface);
// Globalscripts may use VFS script functions
m_ScriptInterface->LoadGlobalScripts();
// File loading
ScriptRequest rq(m_ScriptInterface);
REGISTER_MAPGEN_FUNC_NAME(LoadScripts, "LoadLibrary");
REGISTER_MAPGEN_FUNC_NAME(LoadHeightmap, "LoadHeightmapImage");
REGISTER_MAPGEN_FUNC(LoadMapTerrain);
// Engine constants
// Length of one tile of the terrain grid in metres.
// Useful to transform footprint sizes to the tilegrid coordinate system.
m_ScriptInterface->SetGlobal("TERRAIN_TILE_SIZE", static_cast(TERRAIN_TILE_SIZE));
// Number of impassable tiles at the map border
m_ScriptInterface->SetGlobal("MAP_BORDER_WIDTH", static_cast(MAP_EDGE_TILES));
}
void CMapGeneratorWorker::RegisterScriptFunctions_MapGenerator()
{
ScriptRequest rq(m_ScriptInterface);
// Template functions
REGISTER_MAPGEN_FUNC(GetTemplate);
REGISTER_MAPGEN_FUNC(TemplateExists);
REGISTER_MAPGEN_FUNC(FindTemplates);
REGISTER_MAPGEN_FUNC(FindActorTemplates);
// Progression and profiling
REGISTER_MAPGEN_FUNC(SetProgress);
REGISTER_MAPGEN_FUNC(GetMicroseconds);
REGISTER_MAPGEN_FUNC(ExportMap);
}
#undef REGISTER_MAPGEN_FUNC
#undef REGISTER_MAPGEN_FUNC_NAME
int CMapGeneratorWorker::GetProgress()
{
std::lock_guard lock(m_WorkerMutex);
return m_Progress;
}
double CMapGeneratorWorker::GetMicroseconds()
{
return JS_Now();
}
Script::StructuredClone CMapGeneratorWorker::GetResults()
{
std::lock_guard lock(m_WorkerMutex);
return m_MapData;
}
void CMapGeneratorWorker::ExportMap(JS::HandleValue data)
{
// Copy results
std::lock_guard lock(m_WorkerMutex);
m_MapData = Script::WriteStructuredClone(ScriptRequest(m_ScriptInterface), data);
m_Progress = 0;
}
void CMapGeneratorWorker::SetProgress(int progress)
{
// Copy data
std::lock_guard lock(m_WorkerMutex);
if (progress >= m_Progress)
m_Progress = progress;
else
LOGWARNING("The random map script tried to reduce the loading progress from %d to %d", m_Progress, progress);
}
CParamNode CMapGeneratorWorker::GetTemplate(const std::string& templateName)
{
const CParamNode& templateRoot = m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity");
if (!templateRoot.IsOk())
LOGERROR("Invalid template found for '%s'", templateName.c_str());
return templateRoot;
}
bool CMapGeneratorWorker::TemplateExists(const std::string& templateName)
{
return m_TemplateLoader.TemplateExists(templateName);
}
std::vector CMapGeneratorWorker::FindTemplates(const std::string& path, bool includeSubdirectories)
{
return m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES);
}
std::vector CMapGeneratorWorker::FindActorTemplates(const std::string& path, bool includeSubdirectories)
{
return m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES);
}
bool CMapGeneratorWorker::LoadScripts(const VfsPath& libraryName)
{
// Ignore libraries that are already loaded
if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end())
return true;
// Mark this as loaded, to prevent it recursively loading itself
m_LoadedLibraries.insert(libraryName);
VfsPath path = VfsPath(L"maps/random/") / libraryName / VfsPath();
VfsPaths pathnames;
// Load all scripts in mapgen directory
Status ret = vfs::GetPathnames(g_VFS, path, L"*.js", pathnames);
if (ret == INFO::OK)
{
for (const VfsPath& p : pathnames)
{
LOGMESSAGE("Loading map generator script '%s'", p.string8());
if (!m_ScriptInterface->LoadGlobalScriptFile(p))
{
LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p.string8());
return false;
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
return false;
}
return true;
}
JS::Value CMapGeneratorWorker::LoadHeightmap(const VfsPath& filename)
{
std::vector heightmap;
if (LoadHeightmapImageVfs(filename, heightmap) != INFO::OK)
{
LOGERROR("Could not load heightmap file '%s'", filename.string8());
return JS::UndefinedValue();
}
ScriptRequest rq(m_ScriptInterface);
JS::RootedValue returnValue(rq.cx);
Script::ToJSVal(rq, &returnValue, heightmap);
return returnValue;
}
// See CMapReader::UnpackTerrain, CMapReader::ParseTerrain for the reordering
JS::Value CMapGeneratorWorker::LoadMapTerrain(const VfsPath& filename)
{
ScriptRequest rq(m_ScriptInterface);
if (!VfsFileExists(filename))
{
ScriptException::Raise(rq, "Terrain file \"%s\" does not exist!", filename.string8().c_str());
return JS::UndefinedValue();
}
CFileUnpacker unpacker;
unpacker.Read(filename, "PSMP");
if (unpacker.GetVersion() < CMapIO::FILE_READ_VERSION)
{
ScriptException::Raise(rq, "Could not load terrain file \"%s\" too old version!", filename.string8().c_str());
return JS::UndefinedValue();
}
// unpack size
ssize_t patchesPerSide = (ssize_t)unpacker.UnpackSize();
size_t verticesPerSide = patchesPerSide * PATCH_SIZE + 1;
// unpack heightmap
std::vector heightmap;
heightmap.resize(SQR(verticesPerSide));
unpacker.UnpackRaw(&heightmap[0], SQR(verticesPerSide) * sizeof(u16));
// unpack texture names
size_t textureCount = unpacker.UnpackSize();
std::vector textureNames;
textureNames.reserve(textureCount);
for (size_t i = 0; i < textureCount; ++i)
{
CStr texturename;
unpacker.UnpackString(texturename);
textureNames.push_back(texturename);
}
// unpack texture IDs per tile
ssize_t tilesPerSide = patchesPerSide * PATCH_SIZE;
std::vector tiles;
tiles.resize(size_t(SQR(tilesPerSide)));
unpacker.UnpackRaw(&tiles[0], sizeof(CMapIO::STileDesc) * tiles.size());
// reorder by patches and store and save texture IDs per tile
std::vector textureIDs;
for (ssize_t x = 0; x < tilesPerSide; ++x)
{
size_t patchX = x / PATCH_SIZE;
size_t offX = x % PATCH_SIZE;
for (ssize_t y = 0; y < tilesPerSide; ++y)
{
size_t patchY = y / PATCH_SIZE;
size_t offY = y % PATCH_SIZE;
// m_Priority and m_Tex2Index unused
textureIDs.push_back(tiles[(patchY * patchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)].m_Tex1Index);
}
}
JS::RootedValue returnValue(rq.cx);
Script::CreateObject(
rq,
&returnValue,
"height", heightmap,
"textureNames", textureNames,
"textureIDs", textureIDs);
return returnValue;
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker(nullptr))
{
}
CMapGenerator::~CMapGenerator()
{
delete m_Worker;
}
void CMapGenerator::GenerateMap(const VfsPath& scriptFile, const std::string& settings)
{
m_Worker->Initialize(scriptFile, settings);
}
int CMapGenerator::GetProgress()
{
return m_Worker->GetProgress();
}
Script::StructuredClone CMapGenerator::GetResults()
{
return m_Worker->GetResults();
}
Index: ps/trunk/source/gui/GUIManager.cpp
===================================================================
--- ps/trunk/source/gui/GUIManager.cpp (revision 25441)
+++ ps/trunk/source/gui/GUIManager.cpp (revision 25442)
@@ -1,410 +1,410 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "GUIManager.h"
#include "gui/CGUI.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/XML/Xeromyces.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/StructuredClone.h"
CGUIManager* g_GUI = nullptr;
const CStr CGUIManager::EventNameWindowResized = "WindowResized";
// General TODOs:
//
// A lot of the CGUI data could (and should) be shared between
// multiple pages, instead of treating them as completely independent, to save
// memory and loading time.
// called from main loop when (input) events are received.
// event is passed to other handlers if false is returned.
// trampoline: we don't want to make the HandleEvent implementation static
InReaction gui_handler(const SDL_Event_* ev)
{
if (!g_GUI)
return IN_PASS;
PROFILE("GUI event handler");
return g_GUI->HandleEvent(ev);
}
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
CGUIManager::CGUIManager()
{
m_ScriptContext = g_ScriptContext;
m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIManager", m_ScriptContext));
m_ScriptInterface->SetCallbackData(this);
m_ScriptInterface->LoadGlobalScripts();
if (!CXeromyces::AddValidator(g_VFS, "gui_page", "gui/gui_page.rng"))
LOGERROR("CGUIManager: failed to load GUI page grammar file 'gui/gui_page.rng'");
if (!CXeromyces::AddValidator(g_VFS, "gui", "gui/gui.rng"))
LOGERROR("CGUIManager: failed to load GUI XML grammar file 'gui/gui.rng'");
RegisterFileReloadFunc(ReloadChangedFileCB, this);
}
CGUIManager::~CGUIManager()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
size_t CGUIManager::GetPageCount() const
{
return m_PageStack.size();
}
-void CGUIManager::SwitchPage(const CStrW& pageName, ScriptInterface* srcScriptInterface, JS::HandleValue initData)
+void CGUIManager::SwitchPage(const CStrW& pageName, const ScriptInterface* srcScriptInterface, JS::HandleValue initData)
{
// The page stack is cleared (including the script context where initData came from),
// therefore we have to clone initData.
Script::StructuredClone initDataClone;
if (!initData.isUndefined())
{
ScriptRequest rq(srcScriptInterface);
initDataClone = Script::WriteStructuredClone(rq, initData);
}
m_PageStack.clear();
PushPage(pageName, initDataClone, JS::UndefinedHandleValue);
}
void CGUIManager::PushPage(const CStrW& pageName, Script::StructuredClone initData, JS::HandleValue callbackFunction)
{
// Store the callback handler in the current GUI page before opening the new one
if (!m_PageStack.empty() && !callbackFunction.isUndefined())
m_PageStack.back().SetCallbackFunction(*m_ScriptInterface, callbackFunction);
// Push the page prior to loading its contents, because that may push
// another GUI page on init which should be pushed on top of this new page.
m_PageStack.emplace_back(pageName, initData);
m_PageStack.back().LoadPage(m_ScriptContext);
}
void CGUIManager::PopPage(Script::StructuredClone args)
{
if (m_PageStack.size() < 2)
{
debug_warn(L"Tried to pop GUI page when there's < 2 in the stack");
return;
}
m_PageStack.pop_back();
m_PageStack.back().PerformCallbackFunction(args);
}
CGUIManager::SGUIPage::SGUIPage(const CStrW& pageName, const Script::StructuredClone initData)
: m_Name(pageName), initData(initData), inputs(), gui(), callbackFunction()
{
}
void CGUIManager::SGUIPage::LoadPage(shared_ptr scriptContext)
{
// If we're hotloading then try to grab some data from the previous page
Script::StructuredClone hotloadData;
if (gui)
{
shared_ptr scriptInterface = gui->GetScriptInterface();
ScriptRequest rq(scriptInterface);
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue hotloadDataVal(rq.cx);
ScriptFunction::Call(rq, global, "getHotloadData", &hotloadDataVal);
hotloadData = Script::WriteStructuredClone(rq, hotloadDataVal);
}
g_CursorName = g_DefaultCursor;
inputs.clear();
gui.reset(new CGUI(scriptContext));
gui->AddObjectTypes();
VfsPath path = VfsPath("gui") / m_Name;
inputs.insert(path);
CXeromyces xero;
if (xero.Load(g_VFS, path, "gui_page") != PSRETURN_OK)
// Fail silently (Xeromyces reported the error)
return;
int elmt_page = xero.GetElementID("page");
int elmt_include = xero.GetElementID("include");
XMBElement root = xero.GetRoot();
if (root.GetNodeName() != elmt_page)
{
LOGERROR("GUI page '%s' must have root element ", utf8_from_wstring(m_Name));
return;
}
XERO_ITER_EL(root, node)
{
if (node.GetNodeName() != elmt_include)
{
LOGERROR("GUI page '%s' must only have elements inside ", utf8_from_wstring(m_Name));
continue;
}
CStr8 name = node.GetText();
CStrW nameW = node.GetText().FromUTF8();
PROFILE2("load gui xml");
PROFILE2_ATTR("name: %s", name.c_str());
TIMER(nameW.c_str());
if (name.back() == '/')
{
VfsPath currentDirectory = VfsPath("gui") / nameW;
VfsPaths directories;
vfs::GetPathnames(g_VFS, currentDirectory, L"*.xml", directories);
for (const VfsPath& directory : directories)
gui->LoadXmlFile(directory, inputs);
}
else
{
VfsPath directory = VfsPath("gui") / nameW;
gui->LoadXmlFile(directory, inputs);
}
}
gui->LoadedXmlFiles();
shared_ptr scriptInterface = gui->GetScriptInterface();
ScriptRequest rq(scriptInterface);
JS::RootedValue initDataVal(rq.cx);
JS::RootedValue hotloadDataVal(rq.cx);
JS::RootedValue global(rq.cx, rq.globalValue());
if (initData)
Script::ReadStructuredClone(rq, initData, &initDataVal);
if (hotloadData)
Script::ReadStructuredClone(rq, hotloadData, &hotloadDataVal);
if (Script::HasProperty(rq, global, "init") &&
!ScriptFunction::CallVoid(rq, global, "init", initDataVal, hotloadDataVal))
LOGERROR("GUI page '%s': Failed to call init() function", utf8_from_wstring(m_Name));
}
void CGUIManager::SGUIPage::SetCallbackFunction(ScriptInterface& scriptInterface, JS::HandleValue callbackFunc)
{
if (!callbackFunc.isObject())
{
LOGERROR("Given callback handler is not an object!");
return;
}
ScriptRequest rq(scriptInterface);
if (!JS_ObjectIsFunction(&callbackFunc.toObject()))
{
LOGERROR("Given callback handler is not a function!");
return;
}
callbackFunction = std::make_shared(scriptInterface.GetGeneralJSContext(), callbackFunc);
}
void CGUIManager::SGUIPage::PerformCallbackFunction(Script::StructuredClone args)
{
if (!callbackFunction)
return;
shared_ptr scriptInterface = gui->GetScriptInterface();
ScriptRequest rq(scriptInterface);
JS::RootedObject globalObj(rq.cx, rq.glob);
JS::RootedValue funcVal(rq.cx, *callbackFunction);
// Delete the callback function, so that it is not called again
callbackFunction.reset();
JS::RootedValue argVal(rq.cx);
if (args)
Script::ReadStructuredClone(rq, args, &argVal);
JS::RootedValueVector paramData(rq.cx);
ignore_result(paramData.append(argVal));
JS::RootedValue result(rq.cx);
if(!JS_CallFunctionValue(rq.cx, globalObj, funcVal, paramData, &result))
ScriptException::CatchPending(rq);
}
Status CGUIManager::ReloadChangedFile(const VfsPath& path)
{
for (SGUIPage& p : m_PageStack)
if (p.inputs.find(path) != p.inputs.end())
{
LOGMESSAGE("GUI file '%s' changed - reloading page '%s'", path.string8(), utf8_from_wstring(p.m_Name));
p.LoadPage(m_ScriptContext);
// TODO: this can crash if LoadPage runs an init script which modifies the page stack and breaks our iterators
}
return INFO::OK;
}
Status CGUIManager::ReloadAllPages()
{
// TODO: this can crash if LoadPage runs an init script which modifies the page stack and breaks our iterators
for (SGUIPage& p : m_PageStack)
p.LoadPage(m_ScriptContext);
return INFO::OK;
}
InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
{
// We want scripts to have access to the raw input events, so they can do complex
// processing when necessary (e.g. for unit selection and camera movement).
// Sometimes they'll want to be layered behind the GUI widgets (e.g. to detect mousedowns on the
// visible game area), sometimes they'll want to intercepts events before the GUI (e.g.
// to capture all mouse events until a mouseup after dragging).
// So we call two separate handler functions:
bool handled = false;
{
PROFILE("handleInputBeforeGui");
ScriptRequest rq(*top()->GetScriptInterface());
JS::RootedValue global(rq.cx, rq.globalValue());
if (ScriptFunction::Call(rq, global, "handleInputBeforeGui", handled, *ev, top()->FindObjectUnderMouse()))
if (handled)
return IN_HANDLED;
}
{
PROFILE("handle event in native GUI");
InReaction r = top()->HandleEvent(ev);
if (r != IN_PASS)
return r;
}
{
// We can't take the following lines out of this scope because top() may be another gui page than it was when calling handleInputBeforeGui!
ScriptRequest rq(*top()->GetScriptInterface());
JS::RootedValue global(rq.cx, rq.globalValue());
PROFILE("handleInputAfterGui");
if (ScriptFunction::Call(rq, global, "handleInputAfterGui", handled, *ev))
if (handled)
return IN_HANDLED;
}
return IN_PASS;
}
void CGUIManager::SendEventToAll(const CStr& eventName) const
{
// Save an immutable copy so iterators aren't invalidated by handlers
PageStackType pageStack = m_PageStack;
for (const SGUIPage& p : pageStack)
p.gui->SendEventToAll(eventName);
}
void CGUIManager::SendEventToAll(const CStr& eventName, JS::HandleValueArray paramData) const
{
// Save an immutable copy so iterators aren't invalidated by handlers
PageStackType pageStack = m_PageStack;
for (const SGUIPage& p : pageStack)
p.gui->SendEventToAll(eventName, paramData);
}
void CGUIManager::TickObjects()
{
PROFILE3("gui tick");
// We share the script context with everything else that runs in the same thread.
// This call makes sure we trigger GC regularly even if the simulation is not running.
m_ScriptInterface->GetContext()->MaybeIncrementalGC(1.0f);
// Save an immutable copy so iterators aren't invalidated by tick handlers
PageStackType pageStack = m_PageStack;
for (const SGUIPage& p : pageStack)
p.gui->TickObjects();
}
void CGUIManager::Draw() const
{
PROFILE3_GPU("gui");
for (const SGUIPage& p : m_PageStack)
p.gui->Draw();
}
void CGUIManager::UpdateResolution()
{
// Save an immutable copy so iterators aren't invalidated by event handlers
PageStackType pageStack = m_PageStack;
for (const SGUIPage& p : pageStack)
{
p.gui->UpdateResolution();
p.gui->SendEventToAll(EventNameWindowResized);
}
}
bool CGUIManager::TemplateExists(const std::string& templateName) const
{
return m_TemplateLoader.TemplateExists(templateName);
}
const CParamNode& CGUIManager::GetTemplate(const std::string& templateName)
{
const CParamNode& templateRoot = m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity");
if (!templateRoot.IsOk())
LOGERROR("Invalid template found for '%s'", templateName.c_str());
return templateRoot;
}
// This returns a shared_ptr to make sure the CGUI doesn't get deallocated
// while we're in the middle of calling a function on it (e.g. if a GUI script
// calls SwitchPage)
shared_ptr CGUIManager::top() const
{
ENSURE(m_PageStack.size());
return m_PageStack.back().gui;
}
Index: ps/trunk/source/gui/GUIManager.h
===================================================================
--- ps/trunk/source/gui/GUIManager.h (revision 25441)
+++ ps/trunk/source/gui/GUIManager.h (revision 25442)
@@ -1,180 +1,180 @@
/* Copyright (C) 2021 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 .
*/
#ifndef INCLUDED_GUIMANAGER
#define INCLUDED_GUIMANAGER
#include "lib/file/vfs/vfs_path.h"
#include "lib/input.h"
#include "ps/CStr.h"
#include "ps/TemplateLoader.h"
#include "scriptinterface/StructuredClone.h"
#include
#include
class CGUI;
/**
* External interface to the GUI system.
*
* The GUI consists of a set of pages. Each page is constructed from a
* series of XML files, and is independent from any other page.
* Only one page is active at a time. All events and render requests etc
* will go to the active page. This lets the GUI switch between pre-game menu
* and in-game UI.
*/
class CGUIManager
{
NONCOPYABLE(CGUIManager);
public:
CGUIManager();
~CGUIManager();
shared_ptr GetScriptInterface()
{
return m_ScriptInterface;
}
shared_ptr GetContext() { return m_ScriptContext; }
shared_ptr GetActiveGUI() { return top(); }
/**
* Returns the number of currently open GUI pages.
*/
size_t GetPageCount() const;
/**
* Load a new GUI page and make it active. All current pages will be destroyed.
*/
- void SwitchPage(const CStrW& name, ScriptInterface* srcScriptInterface, JS::HandleValue initData);
+ void SwitchPage(const CStrW& name, const ScriptInterface* srcScriptInterface, JS::HandleValue initData);
/**
* Load a new GUI page and make it active. All current pages will be retained,
* and will still be drawn and receive tick events, but will not receive
* user inputs.
* If given, the callbackHandler function will be executed once this page is closed.
*/
void PushPage(const CStrW& pageName, Script::StructuredClone initData, JS::HandleValue callbackFunc);
/**
* Unload the currently active GUI page, and make the previous page active.
* (There must be at least two pages when you call this.)
*/
void PopPage(Script::StructuredClone args);
/**
* Called when a file has been modified, to hotload changes.
*/
Status ReloadChangedFile(const VfsPath& path);
/**
* Called when we should reload all pages (e.g. translation hotloading update).
*/
Status ReloadAllPages();
/**
* Pass input events to the currently active GUI page.
*/
InReaction HandleEvent(const SDL_Event_* ev);
/**
* See CGUI::SendEventToAll; applies to the currently active page.
*/
void SendEventToAll(const CStr& eventName) const;
void SendEventToAll(const CStr& eventName, JS::HandleValueArray paramData) const;
/**
* See CGUI::TickObjects; applies to @em all loaded pages.
*/
void TickObjects();
/**
* See CGUI::Draw; applies to @em all loaded pages.
*/
void Draw() const;
/**
* See CGUI::UpdateResolution; applies to @em all loaded pages.
*/
void UpdateResolution();
/**
* Check if a template with this name exists
*/
bool TemplateExists(const std::string& templateName) const;
/**
* Retrieve the requested template, used for displaying faction specificities.
*/
const CParamNode& GetTemplate(const std::string& templateName);
private:
struct SGUIPage
{
// COPYABLE, because event handlers may invalidate page stack iterators by open or close pages,
// and event handlers need to be called for the entire stack.
/**
* Initializes the data that will be used to create the CGUI page one or multiple times (hotloading).
*/
SGUIPage(const CStrW& pageName, const Script::StructuredClone initData);
/**
* Create the CGUI with it's own ScriptInterface. Deletes the previous CGUI if it existed.
*/
void LoadPage(shared_ptr scriptContext);
/**
* Sets the callback handler when a new page is opened that will be performed when the page is closed.
*/
void SetCallbackFunction(ScriptInterface& scriptInterface, JS::HandleValue callbackFunc);
/**
* Execute the stored callback function with the given arguments.
*/
void PerformCallbackFunction(Script::StructuredClone args);
CStrW m_Name;
std::unordered_set inputs; // for hotloading
Script::StructuredClone initData; // data to be passed to the init() function
shared_ptr gui; // the actual GUI page
/**
* Function executed by this parent GUI page when the child GUI page it pushed is popped.
* Notice that storing it in the SGUIPage instead of CGUI means that it will survive the hotloading CGUI reset.
*/
shared_ptr callbackFunction;
};
const static CStr EventNameWindowResized;
shared_ptr top() const;
shared_ptr m_ScriptContext;
shared_ptr m_ScriptInterface;
using PageStackType = std::vector;
PageStackType m_PageStack;
CTemplateLoader m_TemplateLoader;
};
extern CGUIManager* g_GUI;
extern InReaction gui_handler(const SDL_Event_* ev);
#endif // INCLUDED_GUIMANAGER
Index: ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp (revision 25441)
+++ ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp (revision 25442)
@@ -1,91 +1,92 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "JSInterface_GUIManager.h"
#include "gui/CGUI.h"
#include "gui/GUIManager.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "ps/GameSetup/Config.h"
#include "scriptinterface/FunctionWrapper.h"
+#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/StructuredClone.h"
namespace JSI_GUIManager
{
// Note that the initData argument may only contain clonable data.
// Functions aren't supported for example!
void PushGuiPage(const ScriptRequest& rq, const std::wstring& name, JS::HandleValue initData, JS::HandleValue callbackFunction)
{
g_GUI->PushPage(name, Script::WriteStructuredClone(rq, initData), callbackFunction);
}
-void SwitchGuiPage(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& name, JS::HandleValue initData)
+void SwitchGuiPage(const ScriptInterface& scriptInterface, const std::wstring& name, JS::HandleValue initData)
{
- g_GUI->SwitchPage(name, pCmptPrivate->pScriptInterface, initData);
+ g_GUI->SwitchPage(name, &scriptInterface, initData);
}
void PopGuiPage(const ScriptRequest& rq, JS::HandleValue args)
{
if (g_GUI->GetPageCount() < 2)
{
ScriptException::Raise(rq, "Can't pop GUI pages when less than two pages are opened!");
return;
}
g_GUI->PopPage(Script::WriteStructuredClone(rq, args));
}
std::wstring SetCursor(const std::wstring& name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
void ResetCursor()
{
g_CursorName = g_DefaultCursor;
}
bool TemplateExists(const std::string& templateName)
{
return g_GUI->TemplateExists(templateName);
}
CParamNode GetTemplate(const std::string& templateName)
{
return g_GUI->GetTemplate(templateName);
}
void RegisterScriptFunctions(const ScriptRequest& rq)
{
ScriptFunction::Register<&PushGuiPage>(rq, "PushGuiPage");
ScriptFunction::Register<&SwitchGuiPage>(rq, "SwitchGuiPage");
ScriptFunction::Register<&PopGuiPage>(rq, "PopGuiPage");
ScriptFunction::Register<&SetCursor>(rq, "SetCursor");
ScriptFunction::Register<&ResetCursor>(rq, "ResetCursor");
ScriptFunction::Register<&TemplateExists>(rq, "TemplateExists");
ScriptFunction::Register<&GetTemplate>(rq, "GetTemplate");
- ScriptFunction::Register<&CGUI::FindObjectByName, &ScriptFunction::ObjectFromCBData>(rq, "GetGUIObjectByName");
- ScriptFunction::Register<&CGUI::SetGlobalHotkey, &ScriptFunction::ObjectFromCBData>(rq, "SetGlobalHotkey");
- ScriptFunction::Register<&CGUI::UnsetGlobalHotkey, &ScriptFunction::ObjectFromCBData>(rq, "UnsetGlobalHotkey");
+ ScriptFunction::Register<&CGUI::FindObjectByName, &ScriptInterface::ObjectFromCBData>(rq, "GetGUIObjectByName");
+ ScriptFunction::Register<&CGUI::SetGlobalHotkey, &ScriptInterface::ObjectFromCBData>(rq, "SetGlobalHotkey");
+ ScriptFunction::Register<&CGUI::UnsetGlobalHotkey, &ScriptInterface::ObjectFromCBData>(rq, "UnsetGlobalHotkey");
}
}
Index: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h (revision 25441)
+++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h (revision 25442)
@@ -1,320 +1,319 @@
/* Copyright (C) 2021 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 .
*/
// This file is included directly into actual implementation files.
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "ps/CLogger.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptExtraHeaders.h"
-#include "scriptinterface/ScriptInterface.h"
+#include "scriptinterface/ScriptRequest.h"
#include
template
JSI_GUIProxy& JSI_GUIProxy::Singleton()
{
static JSI_GUIProxy s;
return s;
}
// Call this for every specialised type. You will need to override IGUIObject::CreateJSObject() in your class interface.
#define DECLARE_GUIPROXY(Type) \
void Type::CreateJSObject() \
{ \
ScriptRequest rq(m_pGUI.GetScriptInterface()); \
using ProxyHandler = JSI_GUIProxy>; \
m_JSObject = ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton())); \
} \
template class JSI_GUIProxy;
// Use a common namespace to avoid duplicating the symbols un-necessarily.
namespace JSInterface_GUIProxy
{
// All proxy objects share a class definition.
JSClass& ClassDefinition()
{
static JSClass c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(1));
return c;
}
// Default implementation of the cache via unordered_map
class MapCache : public GUIProxyProps
{
public:
virtual ~MapCache() {};
virtual bool has(const std::string& name) const override
{
return m_Functions.find(name) != m_Functions.end();
}
virtual JSObject* get(const std::string& name) const override
{
return m_Functions.at(name).get();
}
virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
{
m_Functions[name].init(rq.cx, JS_GetFunctionObject(function));
return true;
}
protected:
std::unordered_map m_Functions;
};
}
template<>
IGUIObject* IGUIProxyObject::FromPrivateSlot(JSObject* obj)
{
if (!obj)
return nullptr;
if (JS_GetClass(obj) != &JSInterface_GUIProxy::ClassDefinition())
return nullptr;
return UnsafeFromPrivateSlot(obj);
}
// The default propcache is a MapCache.
template
struct JSI_GUIProxy::PropCache
{
using type = JSInterface_GUIProxy::MapCache;
};
template
T* JSI_GUIProxy::FromPrivateSlot(const ScriptRequest&, JS::CallArgs& args)
{
// Call the unsafe version - this is only ever called from actual proxy objects.
return IGUIProxyObject::UnsafeFromPrivateSlot(args.thisv().toObjectOrNull());
}
template
bool JSI_GUIProxy::PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
{
using PropertyCache = typename PropCache::type;
// Since we know at compile time what the type actually is, avoid the virtual call.
const PropertyCache* data = static_cast(static_cast(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
if (data->has(propName))
{
vp.setObjectOrNull(data->get(propName));
return true;
}
return false;
}
template
std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface)
{
using PropertyCache = typename PropCache::type;
PropertyCache* data = new PropertyCache();
ScriptRequest rq(scriptInterface);
// Functions common to all children of IGUIObject.
JSI_GUIProxy::CreateFunctions(rq, data);
// Let derived classes register their own interface.
if constexpr (!std::is_same_v)
CreateFunctions(rq, data);
return { &Singleton(), data };
}
template
template
void JSI_GUIProxy::CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name)
{
cache->setFunction(rq, name, ScriptFunction::Create(rq, name.c_str()));
}
template
std::unique_ptr JSI_GUIProxy::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr)
{
js::ProxyOptions options;
options.setClass(&JSInterface_GUIProxy::ClassDefinition());
auto ret = std::make_unique();
ret->m_Ptr = static_cast(ptr);
JS::RootedValue cppObj(rq.cx), data(rq.cx);
cppObj.get().setPrivate(ret->m_Ptr);
data.get().setPrivate(static_cast(dataPtr));
ret->m_Object.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
js::SetProxyReservedSlot(ret->m_Object, 0, data);
return ret;
}
template
bool JSI_GUIProxy::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const
{
- ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
- ScriptRequest rq(*pScriptInterface);
+ ScriptRequest rq(cx);
T* e = IGUIProxyObject::FromPrivateSlot(proxy.get());
if (!e)
return false;
JS::RootedValue idval(rq.cx);
if (!JS_IdToValue(rq.cx, id, &idval))
return false;
std::string propName;
if (!Script::FromJSVal(rq, idval, propName))
return false;
// Return function properties. Specializable.
if (PropGetter(proxy, propName, vp))
return true;
// Use onWhatever to access event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName(propName.substr(2));
std::map>::iterator it = e->m_ScriptHandlers.find(eventName);
if (it == e->m_ScriptHandlers.end())
vp.setNull();
else
vp.setObject(*it->second.get());
return true;
}
if (propName == "parent")
{
IGUIObject* parent = e->GetParent();
if (parent)
vp.set(JS::ObjectValue(*parent->GetJSObject()));
else
vp.set(JS::NullValue());
return true;
}
else if (propName == "children")
{
Script::CreateArray(rq, vp);
for (size_t i = 0; i < e->m_Children.size(); ++i)
Script::SetPropertyInt(rq, vp, i, e->m_Children[i]);
return true;
}
else if (propName == "name")
{
Script::ToJSVal(rq, vp, e->GetName());
return true;
}
else if (e->SettingExists(propName))
{
e->m_Settings[propName]->ToJSVal(rq, vp);
return true;
}
LOGERROR("Property '%s' does not exist!", propName.c_str());
return false;
}
template
bool JSI_GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const
{
T* e = IGUIProxyObject::FromPrivateSlot(proxy.get());
if (!e)
{
LOGERROR("C++ GUI Object could not be found");
return result.fail(JSMSG_OBJECT_REQUIRED);
}
- ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
+ ScriptRequest rq(cx);
JS::RootedValue idval(rq.cx);
if (!JS_IdToValue(rq.cx, id, &idval))
return result.fail(JSMSG_BAD_PROP_ID);
std::string propName;
if (!Script::FromJSVal(rq, idval, propName))
return result.fail(JSMSG_BAD_PROP_ID);
if (propName == "name")
{
std::string value;
if (!Script::FromJSVal(rq, vp, value))
return result.fail(JSMSG_BAD_PROP_ID);
e->SetName(value);
return result.succeed();
}
JS::RootedObject vpObj(cx);
if (vp.isObject())
vpObj = &vp.toObject();
// Use onWhatever to set event handlers
if (propName.substr(0, 2) == "on")
{
if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(&vp.toObject()))
{
LOGERROR("on- event-handlers must be functions");
return result.fail(JSMSG_NOT_FUNCTION);
}
CStr eventName(propName.substr(2));
e->SetScriptHandler(eventName, vpObj);
return result.succeed();
}
if (e->SettingExists(propName))
return e->m_Settings[propName]->FromJSVal(rq, vp, true) ? result.succeed() : result.fail(JSMSG_USER_DEFINED_ERROR);
LOGERROR("Property '%s' does not exist!", propName.c_str());
return result.fail(JSMSG_BAD_PROP_ID);
}
template
bool JSI_GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const
{
T* e = IGUIProxyObject::FromPrivateSlot(proxy.get());
if (!e)
{
LOGERROR("C++ GUI Object could not be found");
return result.fail(JSMSG_OBJECT_REQUIRED);
}
- ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
+ ScriptRequest rq(cx);
JS::RootedValue idval(rq.cx);
if (!JS_IdToValue(rq.cx, id, &idval))
return result.fail(JSMSG_BAD_PROP_ID);
std::string propName;
if (!Script::FromJSVal(rq, idval, propName))
return result.fail(JSMSG_BAD_PROP_ID);
// event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName(propName.substr(2));
e->UnsetScriptHandler(eventName);
return result.succeed();
}
LOGERROR("Only event handlers can be deleted from GUI objects!");
return result.fail(JSMSG_BAD_PROP_ID);
}
Index: ps/trunk/source/gui/Scripting/JSInterface_GUISize.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_GUISize.cpp (revision 25441)
+++ ps/trunk/source/gui/Scripting/JSInterface_GUISize.cpp (revision 25442)
@@ -1,130 +1,129 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "JSInterface_GUISize.h"
#include "ps/CStr.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/Object.h"
JSClass JSI_GUISize::JSI_class = {
"GUISize", 0, &JSI_GUISize::JSI_classops
};
JSClassOps JSI_GUISize::JSI_classops = {
nullptr, nullptr,
nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, JSI_GUISize::construct, nullptr
};
JSFunctionSpec JSI_GUISize::JSI_methods[] =
{
JS_FN("toString", JSI_GUISize::toString, 0, 0),
JS_FS_END
};
void JSI_GUISize::RegisterScriptClass(ScriptInterface& scriptInterface)
{
scriptInterface.DefineCustomObjectType(&JSI_GUISize::JSI_class, JSI_GUISize::construct, 0, nullptr, JSI_GUISize::JSI_methods, nullptr, nullptr);
}
bool JSI_GUISize::construct(JSContext* cx, uint argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
- ScriptRequest rq(*pScriptInterface);
+ ScriptRequest rq(cx);
+ const ScriptInterface& scriptInterface = rq.GetScriptInterface();
- JS::RootedObject obj(rq.cx, pScriptInterface->CreateCustomObject("GUISize"));
+ JS::RootedObject obj(rq.cx, scriptInterface.CreateCustomObject("GUISize"));
if (args.length() == 8)
{
JS_SetProperty(rq.cx, obj, "left", args[0]);
JS_SetProperty(rq.cx, obj, "top", args[1]);
JS_SetProperty(rq.cx, obj, "right", args[2]);
JS_SetProperty(rq.cx, obj, "bottom", args[3]);
JS_SetProperty(rq.cx, obj, "rleft", args[4]);
JS_SetProperty(rq.cx, obj, "rtop", args[5]);
JS_SetProperty(rq.cx, obj, "rright", args[6]);
JS_SetProperty(rq.cx, obj, "rbottom", args[7]);
}
else if (args.length() == 4)
{
JS::RootedValue zero(rq.cx, JS::NumberValue(0));
JS_SetProperty(rq.cx, obj, "left", args[0]);
JS_SetProperty(rq.cx, obj, "top", args[1]);
JS_SetProperty(rq.cx, obj, "right", args[2]);
JS_SetProperty(rq.cx, obj, "bottom", args[3]);
JS_SetProperty(rq.cx, obj, "rleft", zero);
JS_SetProperty(rq.cx, obj, "rtop", zero);
JS_SetProperty(rq.cx, obj, "rright", zero);
JS_SetProperty(rq.cx, obj, "rbottom", zero);
}
else
{
JS::RootedValue zero(rq.cx, JS::NumberValue(0));
JS_SetProperty(rq.cx, obj, "left", zero);
JS_SetProperty(rq.cx, obj, "top", zero);
JS_SetProperty(rq.cx, obj, "right", zero);
JS_SetProperty(rq.cx, obj, "bottom", zero);
JS_SetProperty(rq.cx, obj, "rleft", zero);
JS_SetProperty(rq.cx, obj, "rtop", zero);
JS_SetProperty(rq.cx, obj, "rright", zero);
JS_SetProperty(rq.cx, obj, "rbottom", zero);
}
args.rval().setObject(*obj);
return true;
}
// Produces "10", "-10", "50%", "50%-10", "50%+10", etc
CStr JSI_GUISize::ToPercentString(double pix, double per)
{
if (per == 0)
return CStr::FromDouble(pix);
return CStr::FromDouble(per)+"%"+(pix == 0.0 ? CStr() : pix > 0.0 ? CStr("+")+CStr::FromDouble(pix) : CStr::FromDouble(pix));
}
bool JSI_GUISize::toString(JSContext* cx, uint argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
CStr buffer;
- ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
- ScriptRequest rq(*pScriptInterface);
+ ScriptRequest rq(cx);
double val, valr;
#define SIDE(side) \
Script::GetProperty(rq, args.thisv(), #side, val); \
Script::GetProperty(rq, args.thisv(), "r"#side, valr); \
buffer += ToPercentString(val, valr);
SIDE(left);
buffer += " ";
SIDE(top);
buffer += " ";
SIDE(right);
buffer += " ";
SIDE(bottom);
#undef SIDE
Script::ToJSVal(rq, args.rval(), buffer);
return true;
}
Index: ps/trunk/source/gui/SettingTypes/CGUISize.cpp
===================================================================
--- ps/trunk/source/gui/SettingTypes/CGUISize.cpp (revision 25441)
+++ ps/trunk/source/gui/SettingTypes/CGUISize.cpp (revision 25442)
@@ -1,230 +1,230 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "CGUISize.h"
#include "gui/Scripting/JSInterface_GUISize.h"
#include "ps/CLogger.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptInterface.h"
CGUISize::CGUISize()
: pixel(), percent()
{
}
CGUISize::CGUISize(const CRect& pixel, const CRect& percent)
: pixel(pixel), percent(percent)
{
}
CGUISize CGUISize::Full()
{
return CGUISize(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
}
CRect CGUISize::GetSize(const CRect& parent) const
{
// If it's a 0 0 100% 100% we need no calculations
if (percent == CRect(0.f, 0.f, 100.f, 100.f) && pixel == CRect())
return parent;
CRect client;
// This should probably be cached and not calculated all the time for every object.
client.left = parent.left + (parent.right-parent.left)*percent.left/100.f + pixel.left;
client.top = parent.top + (parent.bottom-parent.top)*percent.top/100.f + pixel.top;
client.right = parent.left + (parent.right-parent.left)*percent.right/100.f + pixel.right;
client.bottom = parent.top + (parent.bottom-parent.top)*percent.bottom/100.f + pixel.bottom;
return client;
}
bool CGUISize::FromString(const CStr& Value)
{
/*
* GUISizes contain a left, top, right, and bottom
* for example: "50%-150 10%+9 50%+150 10%+25" means
* the left edge is at 50% minus 150 pixels, the top
* edge is at 10% plus 9 pixels, the right edge is at
* 50% plus 150 pixels, and the bottom edge is at 10%
* plus 25 pixels.
* All four coordinates are required and can be
* defined only in pixels, only in percents, or some
* combination of both.
*/
// Check the input is only numeric
const char* input = Value.c_str();
CStr buffer = "";
unsigned int coord = 0;
float pixels[4] = {0, 0, 0, 0};
float percents[4] = {0, 0, 0, 0};
for (unsigned int i = 0; i < Value.length(); ++i)
{
switch (input[i])
{
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
buffer.push_back(input[i]);
break;
case '+':
pixels[coord] += buffer.ToFloat();
buffer = "+";
break;
case '-':
pixels[coord] += buffer.ToFloat();
buffer = "-";
break;
case '%':
percents[coord] += buffer.ToFloat();
buffer = "";
break;
case ' ':
pixels[coord] += buffer.ToFloat();
buffer = "";
++coord;
break;
default:
LOGERROR("CGUISize definition may only include numbers. Your input: '%s'", Value.c_str());
return false;
}
if (coord > 3)
{
LOGERROR("Too many CGUISize parameters (4 max). Your input: '%s'", Value.c_str());
return false;
}
}
if (coord < 3)
{
LOGERROR("Too few CGUISize parameters (4 min). Your input: '%s'", Value.c_str());
return false;
}
// Now that we're at the end of the string, flush the remaining buffer.
pixels[coord] += buffer.ToFloat();
// Now store the coords in the right place
pixel.left = pixels[0];
pixel.top = pixels[1];
pixel.right = pixels[2];
pixel.bottom = pixels[3];
percent.left = percents[0];
percent.top = percents[1];
percent.right = percents[2];
percent.bottom = percents[3];
return true;
}
void CGUISize::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret) const
{
- ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(rq.cx)->pScriptInterface;
- ret.setObjectOrNull(pScriptInterface->CreateCustomObject("GUISize"));
+ const ScriptInterface& scriptInterface = rq.GetScriptInterface();
+ ret.setObjectOrNull(scriptInterface.CreateCustomObject("GUISize"));
if (!ret.isObject())
{
ScriptException::Raise(rq, "CGUISize value is not an Object");
return;
}
JS::RootedObject obj(rq.cx, &ret.toObject());
if (!JS_InstanceOf(rq.cx, obj, &JSI_GUISize::JSI_class, nullptr))
{
ScriptException::Raise(rq, "CGUISize value is not a CGUISize class instance");
return;
}
#define P(x, y, z)\
if (!Script::SetProperty(rq, ret, #z, x.y)) \
{ \
ScriptException::Raise(rq, "Could not SetProperty '%s'", #z); \
return; \
}
P(pixel, left, left);
P(pixel, top, top);
P(pixel, right, right);
P(pixel, bottom, bottom);
P(percent, left, rleft);
P(percent, top, rtop);
P(percent, right, rright);
P(percent, bottom, rbottom);
#undef P
}
bool CGUISize::FromJSVal(const ScriptRequest& rq, JS::HandleValue v)
{
if (v.isString())
{
CStrW str;
if (!Script::FromJSVal(rq, v, str))
{
LOGERROR("CGUISize could not read JS string");
return false;
}
if (!FromString(str.ToUTF8()))
{
LOGERROR("CGUISize could not parse JS string");
return false;
}
return true;
}
if (!v.isObject())
{
LOGERROR("CGUISize value is not an String, nor Object");
return false;
}
JS::RootedObject obj(rq.cx, &v.toObject());
if (!JS_InstanceOf(rq.cx, obj, &JSI_GUISize::JSI_class, nullptr))
{
LOGERROR("CGUISize value is not a CGUISize class instance");
return false;
}
#define P(x, y, z) \
if (!Script::GetProperty(rq, v, #z, x.y))\
{\
LOGERROR("CGUISize could not get object property '%s'", #z);\
return false;\
}
P(pixel, left, left);
P(pixel, top, top);
P(pixel, right, right);
P(pixel, bottom, bottom);
P(percent, left, rleft);
P(percent, top, rtop);
P(percent, right, rright);
P(percent, bottom, rbottom);
#undef P
return true;
}
Index: ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp
===================================================================
--- ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp (revision 25441)
+++ ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp (revision 25442)
@@ -1,232 +1,232 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "JSInterface_Lobby.h"
#include "gui/GUIManager.h"
#include "lib/utf8.h"
#include "lobby/IXmppClient.h"
#include "network/NetServer.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Util.h"
#include "scriptinterface/FunctionWrapper.h"
#include "third_party/encryption/pkcs5_pbkdf2.h"
#include
namespace JSI_Lobby
{
bool HasXmppClient()
{
return g_XmppClient;
}
void SetRankedGame(bool isRanked)
{
g_rankedGame = isRanked;
}
#if CONFIG2_LOBBY
void StartXmppClient(const ScriptRequest& rq, const std::wstring& username, const std::wstring& password, const std::wstring& room, const std::wstring& nick, int historyRequestSize)
{
if (g_XmppClient)
{
ScriptException::Raise(rq, "Cannot call StartXmppClient with an already initialized XmppClient!");
return;
}
g_XmppClient =
IXmppClient::create(
g_GUI->GetScriptInterface().get(),
utf8_from_wstring(username),
utf8_from_wstring(password),
utf8_from_wstring(room),
utf8_from_wstring(nick),
historyRequestSize);
g_rankedGame = true;
}
void StartRegisterXmppClient(const ScriptRequest& rq, const std::wstring& username, const std::wstring& password)
{
if (g_XmppClient)
{
ScriptException::Raise(rq, "Cannot call StartRegisterXmppClient with an already initialized XmppClient!");
return;
}
g_XmppClient =
IXmppClient::create(
g_GUI->GetScriptInterface().get(),
utf8_from_wstring(username),
utf8_from_wstring(password),
std::string(),
std::string(),
0,
true);
}
void StopXmppClient(const ScriptRequest& rq)
{
if (!g_XmppClient)
{
ScriptException::Raise(rq, "Cannot call StopXmppClient without an initialized XmppClient!");
return;
}
SAFE_DELETE(g_XmppClient);
g_rankedGame = false;
}
////////////////////////////////////////////////
////////////////////////////////////////////////
IXmppClient* XmppGetter(const ScriptRequest&, JS::CallArgs&)
{
if (!g_XmppClient)
{
LOGERROR("Cannot use XMPPClient functions without an initialized XmppClient!");
return nullptr;
}
return g_XmppClient;
}
-void SendRegisterGame(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue data)
+void SendRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data)
{
if (!g_XmppClient)
{
- ScriptRequest rq(pCmptPrivate->pScriptInterface);
+ ScriptRequest rq(scriptInterface);
ScriptException::Raise(rq, "Cannot call SendRegisterGame without an initialized XmppClient!");
return;
}
// Prevent JS mods to register matches in the lobby that were started with lobby authentication disabled
if (!g_NetServer || !g_NetServer->UseLobbyAuth())
{
LOGERROR("Registering games in the lobby requires lobby authentication to be enabled!");
return;
}
- g_XmppClient->SendIqRegisterGame(*(pCmptPrivate->pScriptInterface), data);
+ g_XmppClient->SendIqRegisterGame(scriptInterface, data);
}
// Unlike other functions, this one just returns Undefined if XmppClient isn't initialised.
JS::Value GuiPollNewMessages(const ScriptInterface& scriptInterface)
{
if (!g_XmppClient)
return JS::UndefinedValue();
return g_XmppClient->GuiPollNewMessages(scriptInterface);
}
// Non-public secure PBKDF2 hash function with salting and 1,337 iterations
//
// TODO: We should use libsodium's crypto_pwhash instead of this. The first reason is that
// libsodium doesn't propose a bare PBKDF2 hash in its API and it's too bad to rely on custom
// code when we have a fully-fledged library available; the second reason is that Argon2 (the
// default algorithm for crypto_pwhash) is better than what we use (and it's the default one
// in the lib for a reason).
// However changing the hashing method should be planned carefully, by trying to login with a
// password hashed the old way, and, if successful, updating the password in the database using
// the new hashing method. Dropping the old hashing code can only be done either by giving users
// a way to reset their password, or by keeping track of successful password updates and dropping
// old unused accounts after some time.
std::string EncryptPassword(const std::string& password, const std::string& username)
{
ENSURE(sodium_init() >= 0);
const int DIGESTSIZE = crypto_hash_sha256_BYTES;
const int ITERATIONS = 1337;
cassert(DIGESTSIZE == 32);
static const unsigned char salt_base[DIGESTSIZE] = {
244, 243, 249, 244, 32, 33, 34, 35, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 133, 123, 234, 123 };
// initialize the salt buffer
unsigned char salt_buffer[DIGESTSIZE] = {0};
crypto_hash_sha256_state state;
crypto_hash_sha256_init(&state);
crypto_hash_sha256_update(&state, salt_base, sizeof(salt_base));
crypto_hash_sha256_update(&state, (unsigned char*)username.c_str(), username.length());
crypto_hash_sha256_final(&state, salt_buffer);
// PBKDF2 to create the buffer
unsigned char encrypted[DIGESTSIZE];
pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS);
return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase();
}
#endif
void RegisterScriptFunctions(const ScriptRequest& rq)
{
// Lobby functions
ScriptFunction::Register<&HasXmppClient>(rq, "HasXmppClient");
ScriptFunction::Register<&SetRankedGame>(rq, "SetRankedGame");
#if CONFIG2_LOBBY // Allow the lobby to be disabled
ScriptFunction::Register<&StartXmppClient>(rq, "StartXmppClient");
ScriptFunction::Register<&StartRegisterXmppClient>(rq, "StartRegisterXmppClient");
ScriptFunction::Register<&StopXmppClient>(rq, "StopXmppClient");
#define REGISTER_XMPP(func, name) \
ScriptFunction::Register<&IXmppClient::func, &XmppGetter>(rq, name)
REGISTER_XMPP(connect, "ConnectXmppClient");
REGISTER_XMPP(disconnect, "DisconnectXmppClient");
REGISTER_XMPP(isConnected, "IsXmppClientConnected");
REGISTER_XMPP(SendIqGetBoardList, "SendGetBoardList");
REGISTER_XMPP(SendIqGetProfile, "SendGetProfile");
REGISTER_XMPP(SendIqGameReport, "SendGameReport");
ScriptFunction::Register<&SendRegisterGame>(rq, "SendRegisterGame");
REGISTER_XMPP(SendIqUnregisterGame, "SendUnregisterGame");
REGISTER_XMPP(SendIqChangeStateGame, "SendChangeStateGame");
REGISTER_XMPP(GUIGetPlayerList, "GetPlayerList");
REGISTER_XMPP(GUIGetGameList, "GetGameList");
REGISTER_XMPP(GUIGetBoardList, "GetBoardList");
REGISTER_XMPP(GUIGetProfile, "GetProfile");
ScriptFunction::Register<&GuiPollNewMessages>(rq, "LobbyGuiPollNewMessages");
REGISTER_XMPP(GuiPollHistoricMessages, "LobbyGuiPollHistoricMessages");
REGISTER_XMPP(GuiPollHasPlayerListUpdate, "LobbyGuiPollHasPlayerListUpdate");
REGISTER_XMPP(SendMUCMessage, "LobbySendMessage");
REGISTER_XMPP(SetPresence, "LobbySetPlayerPresence");
REGISTER_XMPP(SetNick, "LobbySetNick");
REGISTER_XMPP(GetNick, "LobbyGetNick");
REGISTER_XMPP(GetJID, "LobbyGetJID");
REGISTER_XMPP(kick, "LobbyKick");
REGISTER_XMPP(ban, "LobbyBan");
REGISTER_XMPP(GetPresence, "LobbyGetPlayerPresence");
REGISTER_XMPP(GetRole, "LobbyGetPlayerRole");
REGISTER_XMPP(GetRating, "LobbyGetPlayerRating");
REGISTER_XMPP(GetSubject, "LobbyGetRoomSubject");
#undef REGISTER_XMPP
ScriptFunction::Register<&EncryptPassword>(rq, "EncryptPassword");
#endif // CONFIG2_LOBBY
}
}
Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 25441)
+++ ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 25442)
@@ -1,661 +1,662 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "lib/ogl.h"
#include "lib/svn_revision.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/posix/posix_utsname.h"
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/gfx.h"
#include "lib/sysdep/numa.h"
#include "lib/sysdep/os_cpu.h"
#if ARCH_X86_X64
# include "lib/sysdep/arch/x86_x64/topology.h"
#endif
#if CONFIG2_AUDIO
#include "soundmanager/SoundManager.h"
#endif
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Debug.h"
#include "ps/UserReport.h"
#include "ps/VideoMode.h"
#include "scriptinterface/FunctionWrapper.h"
-#include "scriptinterface/Object.h"
#include "scriptinterface/JSON.h"
+#include "scriptinterface/Object.h"
+#include "scriptinterface/ScriptInterface.h"
#if OS_LINUX
#include
#endif
// TODO: Support OpenGL platforms which don't use GLX as well.
#if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
#include
#include
// Define the GLX_MESA_query_renderer macros if built with
// an old Mesa (<10.0) that doesn't provide them
#ifndef GLX_MESA_query_renderer
#define GLX_MESA_query_renderer 1
#define GLX_RENDERER_VENDOR_ID_MESA 0x8183
#define GLX_RENDERER_DEVICE_ID_MESA 0x8184
#define GLX_RENDERER_VERSION_MESA 0x8185
#define GLX_RENDERER_ACCELERATED_MESA 0x8186
#define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187
#define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188
#define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189
#define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A
#define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B
#define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C
#define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D
#define GLX_RENDERER_ID_MESA 0x818E
#endif /* GLX_MESA_query_renderer */
#endif
static void ReportSDL(const ScriptRequest& rq, JS::HandleValue settings);
static void ReportGLLimits(const ScriptRequest& rq, JS::HandleValue settings);
void SetDisableAudio(bool disabled)
{
g_DisableAudio = disabled;
}
void RunHardwareDetection()
{
TIMER(L"RunHardwareDetection");
ScriptInterface scriptInterface("Engine", "HWDetect", g_ScriptContext);
ScriptRequest rq(scriptInterface);
JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
ScriptFunction::Register(rq, "SetDisableAudio");
// Load the detection script:
const wchar_t* scriptName = L"hwdetect/hwdetect.js";
CVFSFile file;
if (file.Load(g_VFS, scriptName) != PSRETURN_OK)
{
LOGERROR("Failed to load hardware detection script");
return;
}
std::string code = file.DecodeUTF8(); // assume it's UTF-8
scriptInterface.LoadScript(scriptName, code);
// Collect all the settings we'll pass to the script:
// (We'll use this same data for the opt-in online reporting system, so it
// includes some fields that aren't directly useful for the hwdetect script)
JS::RootedValue settings(rq.cx);
Script::CreateObject(rq, &settings);
Script::SetProperty(rq, settings, "os_unix", OS_UNIX);
Script::SetProperty(rq, settings, "os_bsd", OS_BSD);
Script::SetProperty(rq, settings, "os_linux", OS_LINUX);
Script::SetProperty(rq, settings, "os_android", OS_ANDROID);
Script::SetProperty(rq, settings, "os_macosx", OS_MACOSX);
Script::SetProperty(rq, settings, "os_win", OS_WIN);
Script::SetProperty(rq, settings, "arch_ia32", ARCH_IA32);
Script::SetProperty(rq, settings, "arch_amd64", ARCH_AMD64);
Script::SetProperty(rq, settings, "arch_arm", ARCH_ARM);
Script::SetProperty(rq, settings, "arch_aarch64", ARCH_AARCH64);
Script::SetProperty(rq, settings, "arch_e2k", ARCH_E2K);
Script::SetProperty(rq, settings, "arch_ppc64", ARCH_PPC64);
#ifdef NDEBUG
Script::SetProperty(rq, settings, "build_debug", 0);
#else
Script::SetProperty(rq, settings, "build_debug", 1);
#endif
Script::SetProperty(rq, settings, "build_opengles", CONFIG2_GLES);
Script::SetProperty(rq, settings, "build_datetime", std::string(__DATE__ " " __TIME__));
Script::SetProperty(rq, settings, "build_revision", std::wstring(svn_revision));
Script::SetProperty(rq, settings, "build_msc", (int)MSC_VERSION);
Script::SetProperty(rq, settings, "build_icc", (int)ICC_VERSION);
Script::SetProperty(rq, settings, "build_gcc", (int)GCC_VERSION);
Script::SetProperty(rq, settings, "build_clang", (int)CLANG_VERSION);
Script::SetProperty(rq, settings, "gfx_card", gfx::CardName());
Script::SetProperty(rq, settings, "gfx_drv_ver", gfx::DriverInfo());
#if CONFIG2_AUDIO
if (g_SoundManager)
{
Script::SetProperty(rq, settings, "snd_card", g_SoundManager->GetSoundCardNames());
Script::SetProperty(rq, settings, "snd_drv_ver", g_SoundManager->GetOpenALVersion());
}
#endif
ReportSDL(scriptInterface, settings);
ReportGLLimits(scriptInterface, settings);
Script::SetProperty(rq, settings, "video_desktop_xres", g_VideoMode.GetDesktopXRes());
Script::SetProperty(rq, settings, "video_desktop_yres", g_VideoMode.GetDesktopYRes());
Script::SetProperty(rq, settings, "video_desktop_bpp", g_VideoMode.GetDesktopBPP());
Script::SetProperty(rq, settings, "video_desktop_freq", g_VideoMode.GetDesktopFreq());
struct utsname un;
uname(&un);
Script::SetProperty(rq, settings, "uname_sysname", std::string(un.sysname));
Script::SetProperty(rq, settings, "uname_release", std::string(un.release));
Script::SetProperty(rq, settings, "uname_version", std::string(un.version));
Script::SetProperty(rq, settings, "uname_machine", std::string(un.machine));
#if OS_LINUX
{
std::ifstream ifs("/etc/lsb-release");
if (ifs.good())
{
std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator());
Script::SetProperty(rq, settings, "linux_release", str);
}
}
#endif
Script::SetProperty(rq, settings, "cpu_identifier", std::string(cpu_IdentifierString()));
Script::SetProperty(rq, settings, "cpu_frequency", os_cpu_ClockFrequency());
Script::SetProperty(rq, settings, "cpu_pagesize", (u32)os_cpu_PageSize());
Script::SetProperty(rq, settings, "cpu_largepagesize", (u32)os_cpu_LargePageSize());
Script::SetProperty(rq, settings, "cpu_numprocs", (u32)os_cpu_NumProcessors());
#if ARCH_X86_X64
Script::SetProperty(rq, settings, "cpu_numpackages", (u32)topology::NumPackages());
Script::SetProperty(rq, settings, "cpu_coresperpackage", (u32)topology::CoresPerPackage());
Script::SetProperty(rq, settings, "cpu_logicalpercore", (u32)topology::LogicalPerCore());
#endif
Script::SetProperty(rq, settings, "numa_numnodes", (u32)numa_NumNodes());
Script::SetProperty(rq, settings, "numa_factor", numa_Factor());
Script::SetProperty(rq, settings, "numa_interleaved", numa_IsMemoryInterleaved());
Script::SetProperty(rq, settings, "ram_total", (u32)os_cpu_MemorySize());
Script::SetProperty(rq, settings, "ram_total_os", (u32)os_cpu_QueryMemorySize());
#if ARCH_X86_X64
Script::SetProperty(rq, settings, "x86_vendor", (u32)x86_x64::Vendor());
Script::SetProperty(rq, settings, "x86_model", (u32)x86_x64::Model());
Script::SetProperty(rq, settings, "x86_family", (u32)x86_x64::Family());
u32 caps0, caps1, caps2, caps3;
x86_x64::GetCapBits(&caps0, &caps1, &caps2, &caps3);
Script::SetProperty(rq, settings, "x86_caps[0]", caps0);
Script::SetProperty(rq, settings, "x86_caps[1]", caps1);
Script::SetProperty(rq, settings, "x86_caps[2]", caps2);
Script::SetProperty(rq, settings, "x86_caps[3]", caps3);
#endif
Script::SetProperty(rq, settings, "timer_resolution", timer_Resolution());
// The version should be increased for every meaningful change.
const int reportVersion = 14;
// Send the same data to the reporting system
g_UserReporter.SubmitReport(
"hwdetect",
reportVersion,
Script::StringifyJSON(rq, &settings, false),
Script::StringifyJSON(rq, &settings, true));
// Run the detection script:
JS::RootedValue global(rq.cx, rq.globalValue());
ScriptFunction::CallVoid(rq, global, "RunHardwareDetection", settings);
}
static void ReportSDL(const ScriptRequest& rq, JS::HandleValue settings)
{
SDL_version build, runtime;
SDL_VERSION(&build);
char version[16];
snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", build.major, build.minor, build.patch);
Script::SetProperty(rq, settings, "sdl_build_version", version);
SDL_GetVersion(&runtime);
snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", runtime.major, runtime.minor, runtime.patch);
Script::SetProperty(rq, settings, "sdl_runtime_version", version);
// This is null in atlas (and further the call triggers an assertion).
const char* backend = g_VideoMode.GetWindow() ? GetSDLSubsystem(g_VideoMode.GetWindow()) : "none";
Script::SetProperty(rq, settings, "sdl_video_backend", backend ? backend : "unknown");
}
static void ReportGLLimits(const ScriptRequest& rq, JS::HandleValue settings)
{
const char* errstr = "(error)";
#define INTEGER(id) do { \
GLint i = -1; \
glGetIntegerv(GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #id, i); \
} while (false)
#define INTEGER2(id) do { \
GLint i[2] = { -1, -1 }; \
glGetIntegerv(GL_##id, i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \
} else { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", i[0]); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", i[1]); \
} \
} while (false)
#define FLOAT(id) do { \
GLfloat f = std::numeric_limits::quiet_NaN(); \
glGetFloatv(GL_##id, &f); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #id, f); \
} while (false)
#define FLOAT2(id) do { \
GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \
glGetFloatv(GL_##id, f); \
if (ogl_SquelchError(GL_INVALID_ENUM)) { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \
} else { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", f[0]); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", f[1]); \
} \
} while (false)
#define STRING(id) do { \
const char* c = (const char*)glGetString(GL_##id); \
if (!c) c = ""; \
if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \
Script::SetProperty(rq, settings, "GL_" #id, std::string(c)); \
} while (false)
#define QUERY(target, pname) do { \
GLint i = -1; \
pglGetQueryivARB(GL_##target, GL_##pname, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, i); \
} while (false)
#define VERTEXPROGRAM(id) do { \
GLint i = -1; \
pglGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \
} while (false)
#define FRAGMENTPROGRAM(id) do { \
GLint i = -1; \
pglGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \
} while (false)
#define BOOL(id) INTEGER(id)
ogl_WarnIfError();
// Core OpenGL 1.3:
// (We don't bother checking extension strings for anything older than 1.3;
// it'll just produce harmless warnings)
STRING(VERSION);
STRING(VENDOR);
STRING(RENDERER);
STRING(EXTENSIONS);
#if !CONFIG2_GLES
INTEGER(MAX_LIGHTS);
INTEGER(MAX_CLIP_PLANES);
// Skip MAX_COLOR_MATRIX_STACK_DEPTH (only in imaging subset)
INTEGER(MAX_MODELVIEW_STACK_DEPTH);
INTEGER(MAX_PROJECTION_STACK_DEPTH);
INTEGER(MAX_TEXTURE_STACK_DEPTH);
#endif
INTEGER(SUBPIXEL_BITS);
#if !CONFIG2_GLES
INTEGER(MAX_3D_TEXTURE_SIZE);
#endif
INTEGER(MAX_TEXTURE_SIZE);
INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
#if !CONFIG2_GLES
INTEGER(MAX_PIXEL_MAP_TABLE);
INTEGER(MAX_NAME_STACK_DEPTH);
INTEGER(MAX_LIST_NESTING);
INTEGER(MAX_EVAL_ORDER);
#endif
INTEGER2(MAX_VIEWPORT_DIMS);
#if !CONFIG2_GLES
INTEGER(MAX_ATTRIB_STACK_DEPTH);
INTEGER(MAX_CLIENT_ATTRIB_STACK_DEPTH);
INTEGER(AUX_BUFFERS);
BOOL(RGBA_MODE);
BOOL(INDEX_MODE);
BOOL(DOUBLEBUFFER);
BOOL(STEREO);
#endif
FLOAT2(ALIASED_POINT_SIZE_RANGE);
#if !CONFIG2_GLES
FLOAT2(SMOOTH_POINT_SIZE_RANGE);
FLOAT(SMOOTH_POINT_SIZE_GRANULARITY);
#endif
FLOAT2(ALIASED_LINE_WIDTH_RANGE);
#if !CONFIG2_GLES
FLOAT2(SMOOTH_LINE_WIDTH_RANGE);
FLOAT(SMOOTH_LINE_WIDTH_GRANULARITY);
// Skip MAX_CONVOLUTION_WIDTH, MAX_CONVOLUTION_HEIGHT (only in imaging subset)
INTEGER(MAX_ELEMENTS_INDICES);
INTEGER(MAX_ELEMENTS_VERTICES);
INTEGER(MAX_TEXTURE_UNITS);
#endif
INTEGER(SAMPLE_BUFFERS);
INTEGER(SAMPLES);
// TODO: compressed texture formats
INTEGER(RED_BITS);
INTEGER(GREEN_BITS);
INTEGER(BLUE_BITS);
INTEGER(ALPHA_BITS);
#if !CONFIG2_GLES
INTEGER(INDEX_BITS);
#endif
INTEGER(DEPTH_BITS);
INTEGER(STENCIL_BITS);
#if !CONFIG2_GLES
INTEGER(ACCUM_RED_BITS);
INTEGER(ACCUM_GREEN_BITS);
INTEGER(ACCUM_BLUE_BITS);
INTEGER(ACCUM_ALPHA_BITS);
#endif
#if !CONFIG2_GLES
// Core OpenGL 2.0 (treated as extensions):
if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
{
FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
}
if (ogl_HaveExtension("GL_ARB_occlusion_query"))
{
QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_ARB_shading_language_100"))
{
STRING(SHADING_LANGUAGE_VERSION_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_shader"))
{
INTEGER(MAX_VERTEX_ATTRIBS_ARB);
INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
INTEGER(MAX_VARYING_FLOATS_ARB);
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
}
if (ogl_HaveExtension("GL_ARB_fragment_shader"))
{
INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") ||
ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
{
INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_TEXTURE_COORDS_ARB);
}
if (ogl_HaveExtension("GL_ARB_draw_buffers"))
{
INTEGER(MAX_DRAW_BUFFERS_ARB);
}
// Core OpenGL 3.0:
if (ogl_HaveExtension("GL_EXT_gpu_shader4"))
{
INTEGER(MIN_PROGRAM_TEXEL_OFFSET); // no _EXT version of these in glext.h
INTEGER(MAX_PROGRAM_TEXEL_OFFSET);
}
if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
{
INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
}
if (ogl_HaveExtension("GL_EXT_framebuffer_multisample"))
{
INTEGER(MAX_SAMPLES_EXT);
}
if (ogl_HaveExtension("GL_EXT_texture_array"))
{
INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT);
}
if (ogl_HaveExtension("GL_EXT_transform_feedback"))
{
INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT);
INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT);
INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT);
}
// Other interesting extensions:
if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query"))
{
QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_ARB_timer_query"))
{
QUERY(TIMESTAMP, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
{
FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
}
if (ogl_HaveExtension("GL_ARB_texture_rectangle"))
{
INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
{
INTEGER(MAX_PROGRAM_MATRICES_ARB);
INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_program"))
{
VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
if (ogl_HaveExtension("GL_ARB_fragment_program"))
{
// The spec seems to say these should be supported, but
// Mesa complains about them so let's not bother
/*
VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
*/
}
}
if (ogl_HaveExtension("GL_ARB_fragment_program"))
{
FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
if (ogl_HaveExtension("GL_ARB_vertex_program"))
{
// The spec seems to say these should be supported, but
// Intel drivers on Windows complain about them so let's not bother
/*
FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
*/
}
}
if (ogl_HaveExtension("GL_ARB_geometry_shader4"))
{
INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB);
INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB);
INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB);
INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB);
INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
}
#else // CONFIG2_GLES
// Core OpenGL ES 2.0:
STRING(SHADING_LANGUAGE_VERSION);
INTEGER(MAX_VERTEX_ATTRIBS);
INTEGER(MAX_VERTEX_UNIFORM_VECTORS);
INTEGER(MAX_VARYING_VECTORS);
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS);
INTEGER(MAX_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_RENDERBUFFER_SIZE);
#endif // CONFIG2_GLES
// TODO: Support OpenGL platforms which don't use GLX as well.
#if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
#define GLXQCR_INTEGER(id) do { \
unsigned int i = UINT_MAX; \
if (pglXQueryCurrentRendererIntegerMESA(id, &i)) \
Script::SetProperty(rq, settings, #id, i); \
} while (false)
#define GLXQCR_INTEGER2(id) do { \
unsigned int i[2] = { UINT_MAX, UINT_MAX }; \
if (pglXQueryCurrentRendererIntegerMESA(id, i)) { \
Script::SetProperty(rq, settings, #id "[0]", i[0]); \
Script::SetProperty(rq, settings, #id "[1]", i[1]); \
} \
} while (false)
#define GLXQCR_INTEGER3(id) do { \
unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \
if (pglXQueryCurrentRendererIntegerMESA(id, i)) { \
Script::SetProperty(rq, settings, #id "[0]", i[0]); \
Script::SetProperty(rq, settings, #id "[1]", i[1]); \
Script::SetProperty(rq, settings, #id "[2]", i[2]); \
} \
} while (false)
#define GLXQCR_STRING(id) do { \
const char* str = pglXQueryCurrentRendererStringMESA(id); \
if (str) \
Script::SetProperty(rq, settings, #id ".string", str); \
} while (false)
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo);
if (ret && wminfo.subsystem == SDL_SYSWM_X11)
{
Display* dpy = wminfo.info.x11.display;
int scrnum = DefaultScreen(dpy);
const char* glxexts = glXQueryExtensionsString(dpy, scrnum);
Script::SetProperty(rq, settings, "glx_extensions", glxexts);
if (strstr(glxexts, "GLX_MESA_query_renderer") && pglXQueryCurrentRendererIntegerMESA && pglXQueryCurrentRendererStringMESA)
{
GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA);
GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA);
GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA);
GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA);
GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA);
GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA);
GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA);
GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA);
GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA);
}
}
#endif // SDL_VIDEO_DRIVER_X11
}
Index: ps/trunk/source/ps/scripting/JSInterface_Mod.cpp
===================================================================
--- ps/trunk/source/ps/scripting/JSInterface_Mod.cpp (revision 25441)
+++ ps/trunk/source/ps/scripting/JSInterface_Mod.cpp (revision 25442)
@@ -1,53 +1,53 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "JSInterface_Mod.h"
#include "ps/Mod.h"
#include "scriptinterface/FunctionWrapper.h"
extern void RestartEngine();
-namespace
+namespace JSI_Mod
{
-bool SetModsAndRestartEngine(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& mods)
+bool SetModsAndRestartEngine(const ScriptInterface& scriptInterface, const std::vector& mods)
{
Mod::ClearIncompatibleMods();
- if (!Mod::CheckAndEnableMods(*(pCmptPrivate->pScriptInterface), mods))
+ if (!Mod::CheckAndEnableMods(scriptInterface, mods))
return false;
RestartEngine();
return true;
}
-}
-bool HasFailedMods(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate))
+bool HasFailedMods()
{
return Mod::GetFailedMods().size() > 0;
}
-void JSI_Mod::RegisterScriptFunctions(const ScriptRequest& rq)
+void RegisterScriptFunctions(const ScriptRequest& rq)
{
ScriptFunction::Register<&Mod::GetEngineInfo>(rq, "GetEngineInfo");
ScriptFunction::Register<&Mod::GetAvailableMods>(rq, "GetAvailableMods");
ScriptFunction::Register<&Mod::GetEnabledMods>(rq, "GetEnabledMods");
ScriptFunction::Register (rq, "HasFailedMods");
ScriptFunction::Register<&Mod::GetFailedMods>(rq, "GetFailedMods");
ScriptFunction::Register<&SetModsAndRestartEngine>(rq, "SetModsAndRestartEngine");
}
+}
Index: ps/trunk/source/scriptinterface/FunctionWrapper.h
===================================================================
--- ps/trunk/source/scriptinterface/FunctionWrapper.h (revision 25441)
+++ ps/trunk/source/scriptinterface/FunctionWrapper.h (revision 25442)
@@ -1,426 +1,406 @@
/* Copyright (C) 2021 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 .
*/
#ifndef INCLUDED_FUNCTIONWRAPPER
#define INCLUDED_FUNCTIONWRAPPER
#include "Object.h"
#include "ScriptConversions.h"
#include "ScriptExceptions.h"
-#include "ScriptInterface.h"
#include "ScriptRequest.h"
+#include
+#include
+
+class ScriptInterface;
+
/**
* This class introduces templates to conveniently wrap C++ functions in JSNative functions.
* This _is_ rather template heavy, so compilation times beware.
* The C++ code can have arbitrary arguments and arbitrary return types, so long
* as they can be converted to/from JS using Script::ToJSVal (FromJSVal respectively),
* and they are default-constructible (TODO: that can probably changed).
* (This could be a namespace, but I like being able to specify public/private).
*/
class ScriptFunction {
private:
ScriptFunction() = delete;
ScriptFunction(const ScriptFunction&) = delete;
ScriptFunction(ScriptFunction&&) = delete;
/**
* In JS->C++ calls, types are converted using FromJSVal,
* and this requires them to be default-constructible (as that function takes an out parameter)
* thus constref needs to be removed when defining the tuple.
* Exceptions are:
* - const ScriptRequest& (as the first argument only, for implementation simplicity).
* - const ScriptInterface& (as the first argument only, for implementation simplicity).
* - JS::HandleValue
*/
template
using type_transform = std::conditional_t<
std::is_same_v || std::is_same_v,
T,
std::remove_const_t>
>;
/**
* Convenient struct to get info on a [class] [const] function pointer.
* TODO VS19: I ran into a really weird bug with an auto specialisation on this taking function pointers.
* It'd be good to add it back once we upgrade.
*/
template struct args_info;
template
struct args_info
{
static constexpr const size_t nb_args = sizeof...(Types);
using return_type = R;
using object_type = void;
using arg_types = std::tuple...>;
};
template
struct args_info : public args_info { using object_type = C; };
template
struct args_info : public args_info {};
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* DoConvertFromJS takes a type, a JS argument, and converts.
* The type T must be default constructible (except for HandleValue, which is handled specially).
* (possible) TODO: this could probably be changed if FromJSVal had a different signature.
* @param went_ok - true if the conversion succeeded and went_ok was true before, false otherwise.
*/
template
static std::tuple DoConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok)
{
// No need to convert JS values.
if constexpr (std::is_same_v)
{
// Default-construct values that aren't passed by JS.
// TODO: this should perhaps be removed, as it's distinct from C++ default values and kind of tricky.
if (idx >= args.length())
return std::forward_as_tuple(JS::UndefinedHandleValue);
else
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
return std::forward_as_tuple(args[idx]); // This passes the null handle value if idx is beyond the length of args.
}
}
else
{
// Default-construct values that aren't passed by JS.
// TODO: this should perhaps be removed, as it's distinct from C++ default values and kind of tricky.
if (idx >= args.length())
return std::forward_as_tuple(T{});
else
{
T ret;
went_ok &= Script::FromJSVal(rq, args[idx], ret);
return std::forward_as_tuple(ret);
}
}
}
/**
* Recursive wrapper: calls DoConvertFromJS for type T and recurses.
*/
template
static std::tuple DoConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok)
{
return std::tuple_cat(DoConvertFromJS(rq, args, went_ok), DoConvertFromJS(rq, args, went_ok));
}
/**
* ConvertFromJS is a wrapper around DoConvertFromJS, and serves to:
* - unwrap the tuple types as a parameter pack
- * - handle specific cases for the first argument (cmptPrivate, ScriptRequest).
+ * - handle specific cases for the first argument (ScriptRequest, ...).
*
* Trick: to unpack the types of the tuple as a parameter pack, we deduce them from the function signature.
* To do that, we want the tuple in the arguments, but we don't want to actually have to default-instantiate,
* so we'll pass a nullptr that's static_cast to what we want.
*/
template
- static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate*, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
+ static std::tuple ConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
return {};
}
else
return DoConvertFromJS<0, Types...>(rq, args, went_ok);
}
- // Overloads for CmptPrivate* first argument.
- template
- static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate* cmptPrivate, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
- {
- if constexpr (sizeof...(Types) == 0)
- {
- // GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
- UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
- return std::forward_as_tuple(cmptPrivate);
- }
- else
- return std::tuple_cat(std::forward_as_tuple(cmptPrivate), DoConvertFromJS<0, Types...>(rq, args, went_ok));
- }
-
// Overloads for ScriptRequest& first argument.
template
- static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate*, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
+ static std::tuple ConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(args); UNUSED2(went_ok);
return std::forward_as_tuple(rq);
}
else
return std::tuple_cat(std::forward_as_tuple(rq), DoConvertFromJS<0, Types...>(rq, args, went_ok));
}
// Overloads for ScriptInterface& first argument.
template
- static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate* cmptPrivate, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
+ static std::tuple ConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
- return std::forward_as_tuple(*cmptPrivate->pScriptInterface);
+ return std::forward_as_tuple(rq.GetScriptInterface());
}
else
- return std::tuple_cat(std::forward_as_tuple(*cmptPrivate->pScriptInterface), DoConvertFromJS<0, Types...>(rq, args, went_ok));
+ return std::tuple_cat(std::forward_as_tuple(rq.GetScriptInterface()), DoConvertFromJS<0, Types...>(rq, args, went_ok));
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* Wrap std::apply for the case where we have an object method or a regular function.
*/
template
static typename args_info::return_type call(T* object, tuple& args)
{
if constexpr(std::is_same_v)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(object);
return std::apply(callable, args);
}
else
return std::apply(callable, std::tuple_cat(std::forward_as_tuple(*object), args));
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
struct IgnoreResult_t {};
static inline IgnoreResult_t IgnoreResult;
/**
* Recursive helper to call AssignOrToJSVal
*/
template
static void AssignOrToJSValHelper(const ScriptRequest& rq, JS::MutableHandleValueVector argv, const T& a, const Ts&... params)
{
Script::ToJSVal(rq, argv[i], a);
AssignOrToJSValHelper(rq, argv, params...);
}
template
static void AssignOrToJSValHelper(const ScriptRequest& UNUSED(rq), JS::MutableHandleValueVector UNUSED(argv))
{
static_assert(sizeof...(Ts) == 0);
// Nop, for terminating the template recursion.
}
/**
* Wrapper around calling a JS function from C++.
* Arguments are const& to avoid lvalue/rvalue issues, and so can't be used as out-parameters.
* In particular, the problem is that Rooted are deduced as Rooted, not Handle, and so can't be copied.
* This could be worked around with more templates, but it doesn't seem particularly worth doing.
*/
template
static bool Call_(const ScriptRequest& rq, JS::HandleValue val, const char* name, R& ret, const Args&... args)
{
JS::RootedObject obj(rq.cx);
if (!JS_ValueToObject(rq.cx, val, &obj) || !obj)
return false;
// Check that the named function actually exists, to avoid ugly JS error reports
// when calling an undefined value
bool found;
if (!JS_HasProperty(rq.cx, obj, name, &found) || !found)
return false;
JS::RootedValueVector argv(rq.cx);
ignore_result(argv.resize(sizeof...(Args)));
AssignOrToJSValHelper<0>(rq, &argv, args...);
bool success;
if constexpr (std::is_same_v)
success = JS_CallFunctionName(rq.cx, obj, name, argv, ret);
else
{
JS::RootedValue jsRet(rq.cx);
success = JS_CallFunctionName(rq.cx, obj, name, argv, &jsRet);
if constexpr (!std::is_same_v)
{
if (success)
Script::FromJSVal(rq, jsRet, ret);
}
else
UNUSED2(ret); // VS2017 complains.
}
// Even if everything succeeded, there could be pending exceptions
return !ScriptException::CatchPending(rq) && success;
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
public:
template
using ObjectGetter = T*(*)(const ScriptRequest&, JS::CallArgs&);
// TODO: the fact that this takes class and not auto is to work around an odd VS17 bug.
// It can be removed with VS19.
template
using GetterFor = ObjectGetter::object_type>;
/**
* The meat of this file. This wraps a C++ function into a JSNative,
* so that it can be called from JS and manipulated in Spidermonkey.
* Most C++ functions can be directly wrapped, so long as their arguments are
* convertible from JS::Value and their return value is convertible to JS::Value (or void)
- * The C++ function may optionally take const ScriptRequest& or CmptPrivate* as its first argument.
+ * The C++ function may optionally take const ScriptRequest& or ScriptInterface& as its first argument.
* The function may be an object method, in which case you need to pass an appropriate getter
*
* Optimisation note: the ScriptRequest object is created even without arguments,
* as it's necessary for IsExceptionPending.
*
* @param thisGetter to get the object, if necessary.
*/
template thisGetter = nullptr>
static bool ToJSNative(JSContext* cx, unsigned argc, JS::Value* vp)
{
using ObjType = typename args_info::object_type;
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- ScriptInterface* scriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
- ScriptRequest rq(*scriptInterface);
+ ScriptRequest rq(cx);
// If the callable is an object method, we must specify how to fetch the object.
static_assert(std::is_same_v::object_type, void> || thisGetter != nullptr,
"ScriptFunction::Register - No getter specified for object method");
// GCC 7 triggers spurious warnings
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress"
#endif
ObjType* obj = nullptr;
if constexpr (thisGetter != nullptr)
{
obj = thisGetter(rq, args);
if (!obj)
return false;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
bool went_ok = true;
- typename args_info::arg_types outs = ConvertFromJS(ScriptInterface::GetScriptInterfaceAndCBData(cx), rq, args, went_ok, static_cast::arg_types*>(nullptr));
+ typename args_info::arg_types outs = ConvertFromJS(rq, args, went_ok, static_cast::arg_types*>(nullptr));
if (!went_ok)
return false;
/**
* TODO: error handling isn't standard, and since this can call any C++ function,
* there's no simple obvious way to deal with it.
* For now we check for pending JS exceptions, but it would probably be nicer
* to standardise on something, or perhaps provide an "errorHandler" here.
*/
if constexpr (std::is_same_v::return_type>)
call(obj, outs);
else if constexpr (std::is_same_v::return_type>)
args.rval().set(call(obj, outs));
else
Script::ToJSVal(rq, args.rval(), call(obj, outs));
return !ScriptException::IsPending(rq);
}
/**
* Call a JS function @a name, property of object @a val, with the arguments @a args.
* @a ret will be updated with the return value, if any.
* @return the success (or failure) thereof.
*/
template
static bool Call(const ScriptRequest& rq, JS::HandleValue val, const char* name, R& ret, const Args&... args)
{
return Call_(rq, val, name, ret, std::forward(args)...);
}
// Specialisation for MutableHandleValue return.
template
static bool Call(const ScriptRequest& rq, JS::HandleValue val, const char* name, JS::MutableHandleValue ret, const Args&... args)
{
return Call_(rq, val, name, ret, std::forward(args)...);
}
/**
* Call a JS function @a name, property of object @a val, with the arguments @a args.
* @return the success (or failure) thereof.
*/
template
static bool CallVoid(const ScriptRequest& rq, JS::HandleValue val, const char* name, const Args&... args)
{
return Call(rq, val, name, IgnoreResult, std::forward(args)...);
}
/**
* Return a function spec from a C++ function.
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static JSFunctionSpec Wrap(const char* name)
{
return JS_FN(name, (&ToJSNative), args_info::nb_args, flags);
}
/**
* Return a JSFunction from a C++ function.
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static JSFunction* Create(const ScriptRequest& rq, const char* name)
{
return JS_NewFunction(rq.cx, &ToJSNative, args_info::nb_args, flags, name);
}
/**
* Register a function on the native scope (usually 'Engine').
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static void Register(const ScriptRequest& rq, const char* name)
{
JS_DefineFunction(rq.cx, rq.nativeScope, name, &ToJSNative, args_info::nb_args, flags);
}
/**
* Register a function on @param scope.
* Prefer the version taking ScriptRequest unless you have a good reason not to.
* @see Register
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static void Register(JSContext* cx, JS::HandleObject scope, const char* name)
{
JS_DefineFunction(cx, scope, name, &ToJSNative, args_info::nb_args, flags);
}
-
- /**
- * Convert the CmptPrivate callback data to T*
- */
- template
- static T* ObjectFromCBData(const ScriptRequest& rq, JS::CallArgs&)
- {
- return static_cast(ScriptInterface::GetScriptInterfaceAndCBData(rq.cx)->pCBData);
- }
};
#endif // INCLUDED_FUNCTIONWRAPPER
Index: ps/trunk/source/scriptinterface/ScriptInterface.cpp
===================================================================
--- ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 25441)
+++ ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 25442)
@@ -1,684 +1,706 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "FunctionWrapper.h"
#include "ScriptContext.h"
#include "ScriptExtraHeaders.h"
#include "ScriptInterface.h"
#include "ScriptStats.h"
#include "StructuredClone.h"
#include "lib/debug.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/utf16string.h"
#include