Index: ps/trunk/source/gui/Scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/Scripting/ScriptFunctions.cpp +++ ps/trunk/source/gui/Scripting/ScriptFunctions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* 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 @@ -50,9 +50,11 @@ */ void GuiScriptingInit(ScriptInterface& scriptInterface) { + ScriptRequest rq(scriptInterface); + JSI_GUISize::RegisterScriptClass(scriptInterface); JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); - JSI_Console::RegisterScriptFunctions(scriptInterface); + JSI_Console::RegisterScriptFunctions(rq); JSI_Debug::RegisterScriptFunctions(scriptInterface); JSI_GUIManager::RegisterScriptFunctions(scriptInterface); JSI_Game::RegisterScriptFunctions(scriptInterface); Index: ps/trunk/source/ps/CConsole.h =================================================================== --- ps/trunk/source/ps/CConsole.h +++ ps/trunk/source/ps/CConsole.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* 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 @@ -82,7 +82,7 @@ const wchar_t* GetBuffer(); void FlushBuffer(); - bool IsActive() { return m_bVisible; } + bool IsActive() const { return m_bVisible; } int m_iFontHeight; int m_iFontWidth; Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/HWDetect.cpp +++ ps/trunk/source/ps/GameSetup/HWDetect.cpp @@ -17,8 +17,6 @@ #include "precompiled.h" -#include "scriptinterface/ScriptInterface.h" - #include "lib/ogl.h" #include "lib/svn_revision.h" #include "lib/timer.h" @@ -45,6 +43,8 @@ #include "ps/scripting/JSInterface_Debug.h" #include "ps/UserReport.h" #include "ps/VideoMode.h" +#include "scriptinterface/FunctionWrapper.h" +#include "scriptinterface/ScriptInterface.h" // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES @@ -74,7 +74,7 @@ static void ReportSDL(const ScriptInterface& scriptInterface, JS::HandleValue settings); static void ReportGLLimits(const ScriptInterface& scriptInterface, JS::HandleValue settings); -void SetDisableAudio(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), bool disabled) +void SetDisableAudio(bool disabled) { g_DisableAudio = disabled; } @@ -90,7 +90,7 @@ JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); - scriptInterface.RegisterFunction("SetDisableAudio"); + ScriptFunction::Register(rq, "SetDisableAudio"); // Load the detection script: Index: ps/trunk/source/ps/scripting/JSInterface_Console.h =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Console.h +++ ps/trunk/source/ps/scripting/JSInterface_Console.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* 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 @@ -18,15 +18,11 @@ #ifndef INCLUDED_JSI_CONSOLE #define INCLUDED_JSI_CONSOLE -#include "scriptinterface/ScriptInterface.h" +class ScriptRequest; namespace JSI_Console { - bool CheckGlobalInitialized(); - bool GetVisibleEnabled(ScriptInterface::CmptPrivate* pCmptPrivate); - void SetVisibleEnabled(ScriptInterface::CmptPrivate* pCmptPrivate, bool Enabled); - - void RegisterScriptFunctions(const ScriptInterface& scriptInterface); + void RegisterScriptFunctions(const ScriptRequest& rq); } #endif // INCLUDED_JSI_CONSOLE Index: ps/trunk/source/ps/scripting/JSInterface_Console.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Console.cpp +++ ps/trunk/source/ps/scripting/JSInterface_Console.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* 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 @@ -21,34 +21,23 @@ #include "ps/CConsole.h" #include "ps/CLogger.h" -#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/FunctionWrapper.h" -bool JSI_Console::CheckGlobalInitialized() +namespace +{ +CConsole* ConsoleGetter(const ScriptRequest&, JS::CallArgs&) { if (!g_Console) { LOGERROR("Trying to access the console when it's not initialized!"); - return false; + return nullptr; } - return true; -} - -bool JSI_Console::GetVisibleEnabled(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) -{ - if (!CheckGlobalInitialized()) - return false; - return g_Console->IsActive(); + return g_Console; } - -void JSI_Console::SetVisibleEnabled(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), bool Enabled) -{ - if (!CheckGlobalInitialized()) - return; - g_Console->SetVisible(Enabled); } -void JSI_Console::RegisterScriptFunctions(const ScriptInterface& scriptInterface) +void JSI_Console::RegisterScriptFunctions(const ScriptRequest& rq) { - scriptInterface.RegisterFunction("Console_GetVisibleEnabled"); - scriptInterface.RegisterFunction("Console_SetVisibleEnabled"); + ScriptFunction::Register<&CConsole::IsActive, ConsoleGetter>(rq, "Console_GetVisibleEnabled"); + ScriptFunction::Register<&CConsole::SetVisible, ConsoleGetter>(rq, "Console_SetVisibleEnabled"); } Index: ps/trunk/source/ps/scripting/JSInterface_Hotkey.h =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Hotkey.h +++ ps/trunk/source/ps/scripting/JSInterface_Hotkey.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* 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 @@ -18,11 +18,11 @@ #ifndef INCLUDED_JSI_HOTKEY #define INCLUDED_JSI_HOTKEY -#include "scriptinterface/ScriptInterface.h" +class ScriptRequest; namespace JSI_Hotkey { - void RegisterScriptFunctions(const ScriptInterface& ScriptInterface); + void RegisterScriptFunctions(const ScriptRequest& rq); } #endif // INCLUDED_JSI_HOTKEY Index: ps/trunk/source/ps/scripting/JSInterface_Hotkey.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Hotkey.cpp +++ ps/trunk/source/ps/scripting/JSInterface_Hotkey.cpp @@ -24,6 +24,7 @@ #include "ps/ConfigDB.h" #include "ps/Hotkey.h" #include "ps/KeyName.h" +#include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptConversions.h" #include @@ -66,13 +67,13 @@ ToJSVal_unordered_map(rq, ret, val); } +namespace +{ /** * @return a (js) object mapping hotkey name (from cfg files) to a list ofscancode names */ -JS::Value GetHotkeyMap(ScriptInterface::CmptPrivate* pCmptPrivate) +JS::Value GetHotkeyMap(const ScriptRequest& rq) { - ScriptRequest rq(*pCmptPrivate->pScriptInterface); - JS::RootedValue hotkeyMap(rq.cx); std::unordered_map>> hotkeys; @@ -89,7 +90,7 @@ if (keymap.size() < 2 || keymap[0] < keymap[1]) hotkeys[mapping.name].emplace_back(keymap); } - pCmptPrivate->pScriptInterface->ToJSVal(rq, &hotkeyMap, hotkeys); + ScriptInterface::ToJSVal(rq, &hotkeyMap, hotkeys); return hotkeyMap; } @@ -97,10 +98,8 @@ /** * @return a (js) object mapping scancode names to their locale-dependent name. */ -JS::Value GetScancodeKeyNames(ScriptInterface::CmptPrivate* pCmptPrivate) +JS::Value GetScancodeKeyNames(const ScriptRequest& rq) { - ScriptRequest rq(*pCmptPrivate->pScriptInterface); - JS::RootedValue obj(rq.cx); std::unordered_map map; @@ -108,24 +107,21 @@ // This is slightly wasteful but should be fine overall, they are dense. for (int i = 0; i < MOUSE_LAST; ++i) map[FindScancodeName(static_cast(i))] = FindKeyName(static_cast(i)); - pCmptPrivate->pScriptInterface->ToJSVal(rq, &obj, map); + ScriptInterface::ToJSVal(rq, &obj, map); return obj; } -void ReloadHotkeys(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) +void ReloadHotkeys() { UnloadHotkeys(); LoadHotkeys(g_ConfigDB); } -JS::Value GetConflicts(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue combination) +JS::Value GetConflicts(const ScriptRequest& rq, JS::HandleValue combination) { - ScriptInterface* scriptInterface = pCmptPrivate->pScriptInterface; - ScriptRequest rq(*scriptInterface); - std::vector keys; - if (!scriptInterface->FromJSVal(rq, combination, keys)) + if (!ScriptInterface::FromJSVal(rq, combination, keys)) { LOGERROR("Invalid hotkey combination"); return JS::NullValue(); @@ -161,14 +157,15 @@ return JS::NullValue(); JS::RootedValue ret(rq.cx); - scriptInterface->ToJSVal(rq, &ret, conflicts); + ScriptInterface::ToJSVal(rq, &ret, conflicts); return ret; } +} -void JSI_Hotkey::RegisterScriptFunctions(const ScriptInterface& scriptInterface) +void JSI_Hotkey::RegisterScriptFunctions(const ScriptRequest& rq) { - scriptInterface.RegisterFunction("GetHotkeyMap"); - scriptInterface.RegisterFunction("GetScancodeKeyNames"); - scriptInterface.RegisterFunction("ReloadHotkeys"); - scriptInterface.RegisterFunction("GetConflicts"); + ScriptFunction::Register<&GetHotkeyMap>(rq, "GetHotkeyMap"); + ScriptFunction::Register<&GetScancodeKeyNames>(rq, "GetScancodeKeyNames"); + ScriptFunction::Register<&ReloadHotkeys>(rq, "ReloadHotkeys"); + ScriptFunction::Register<&GetConflicts>(rq, "GetConflicts"); } Index: ps/trunk/source/ps/scripting/JSInterface_Mod.h =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Mod.h +++ ps/trunk/source/ps/scripting/JSInterface_Mod.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* 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 @@ -18,17 +18,11 @@ #ifndef INCLUDED_JSI_MOD #define INCLUDED_JSI_MOD -#include "ps/CStr.h" -#include "scriptinterface/ScriptInterface.h" +class ScriptRequest; namespace JSI_Mod { - void RegisterScriptFunctions(const ScriptInterface& scriptInterface); - - JS::Value GetEngineInfo(ScriptInterface::CmptPrivate* pCmptPrivate); - JS::Value GetAvailableMods(ScriptInterface::CmptPrivate* pCmptPrivate); - void RestartEngine(ScriptInterface::CmptPrivate* pCmptPrivate); - void SetMods(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& mods); + void RegisterScriptFunctions(const ScriptRequest& rq); } #endif // INCLUDED_JSI_MOD Index: ps/trunk/source/ps/scripting/JSInterface_Mod.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Mod.cpp +++ ps/trunk/source/ps/scripting/JSInterface_Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* 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 @@ -20,11 +20,14 @@ #include "JSInterface_Mod.h" #include "ps/Mod.h" +#include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptInterface.h" extern void RestartEngine(); -JS::Value JSI_Mod::GetEngineInfo(ScriptInterface::CmptPrivate* pCmptPrivate) +namespace +{ +JS::Value GetEngineInfo(ScriptInterface::CmptPrivate* pCmptPrivate) { return Mod::GetEngineInfo(*(pCmptPrivate->pScriptInterface)); } @@ -39,25 +42,21 @@ * @return JS object with available mods as the keys of the modname.json * properties. */ -JS::Value JSI_Mod::GetAvailableMods(ScriptInterface::CmptPrivate* pCmptPrivate) +JS::Value GetAvailableMods(ScriptInterface::CmptPrivate* pCmptPrivate) { return Mod::GetAvailableMods(*(pCmptPrivate->pScriptInterface)); } -void JSI_Mod::RestartEngine(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) -{ - ::RestartEngine(); -} - -void JSI_Mod::SetMods(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::vector& mods) +void SetMods(const std::vector& mods) { g_modsLoaded = mods; } +} -void JSI_Mod::RegisterScriptFunctions(const ScriptInterface& scriptInterface) +void JSI_Mod::RegisterScriptFunctions(const ScriptRequest& rq) { - scriptInterface.RegisterFunction("GetEngineInfo"); - scriptInterface.RegisterFunction("GetAvailableMods"); - scriptInterface.RegisterFunction("RestartEngine"); - scriptInterface.RegisterFunction, &JSI_Mod::SetMods>("SetMods"); + ScriptFunction::Register<&GetEngineInfo>(rq, "GetEngineInfo"); + ScriptFunction::Register<&GetAvailableMods>(rq, "GetAvailableMods"); + ScriptFunction::Register<&RestartEngine>(rq, "RestartEngine"); + ScriptFunction::Register<&SetMods>(rq, "SetMods"); } Index: ps/trunk/source/scriptinterface/FunctionWrapper.h =================================================================== --- ps/trunk/source/scriptinterface/FunctionWrapper.h +++ ps/trunk/source/scriptinterface/FunctionWrapper.h @@ -0,0 +1,276 @@ +/* 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 "ScriptInterface.h" +#include "ScriptExceptions.h" + +/** + * 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 ScriptInterface::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) + * Exceptions are: + * - const ScriptRequest& (as the first argument only, for implementation simplicity). + * - JS::HandleValue + */ + template + using type_transform = std::conditional_t, const ScriptRequest&, + 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) + { + // 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 &= ScriptInterface::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). + * + * 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*) + { + 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*) + { + 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)); + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + /** + * 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)); + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// +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 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); + +// GCC 7 triggers spurious warnings +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress" + ObjType* obj = nullptr; + if constexpr (thisGetter != nullptr) + { + obj = thisGetter(rq, args); + if (!obj) + return false; + } +#pragma GCC diagnostic pop + + bool went_ok = true; + typename args_info::arg_types outs = ConvertFromJS(ScriptInterface::GetScriptInterfaceAndCBData(cx), 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 + ScriptInterface::ToJSVal(rq, args.rval(), call(obj, outs)); + + return !ScriptException::IsPending(rq); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.h +++ ps/trunk/source/scriptinterface/ScriptInterface.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* 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 @@ -83,6 +83,7 @@ JS::Value globalValue() const; JSContext* cx; JSObject* glob; + JS::HandleObject nativeScope; private: JS::Realm* m_formerRealm; }; Index: ps/trunk/source/scriptinterface/ScriptInterface.cpp =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.cpp +++ ps/trunk/source/scriptinterface/ScriptInterface.cpp @@ -72,7 +72,7 @@ }; ScriptRequest::ScriptRequest(const ScriptInterface& scriptInterface) : - cx(scriptInterface.m->m_cx) + cx(scriptInterface.m->m_cx), nativeScope(scriptInterface.m->m_nativeScope) { m_formerRealm = JS::EnterRealm(cx, scriptInterface.m->m_glob); glob = JS::CurrentGlobalOrNull(cx); Index: ps/trunk/source/scriptinterface/tests/test_FunctionWrapper.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_FunctionWrapper.h +++ ps/trunk/source/scriptinterface/tests/test_FunctionWrapper.h @@ -0,0 +1,107 @@ +/* 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 "lib/self_test.h" + +#include "scriptinterface/FunctionWrapper.h" + +class TestFunctionWrapper : public CxxTest::TestSuite +{ +public: + + // TODO C++20: use lambda functions directly, names are 'N params, void/returns'. + static void _1p_v(int) {}; + static void _3p_v(int, bool, std::string) {}; + static int _3p_r(int a, bool, std::string) { return a; }; + + static void _0p_v() {}; + static int _0p_r() { return 1; }; + + void test_simple_wrappers() + { + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + } + + static void _handle(JS::HandleValue) {}; + static void _handle_2(int, JS::HandleValue, bool) {}; + + static void _cmpt_private(ScriptInterface::CmptPrivate*) {}; + static int _cmpt_private_2(ScriptInterface::CmptPrivate*, int a, bool) { return a; }; + + static void _script_request(const ScriptRequest&) {}; + static int _script_request_2(const ScriptRequest&, int a, bool) { return a; }; + + void test_special_wrappers() + { + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + } + + class test_method + { + public: + void method_1() {}; + int method_2(int, const int&) { return 4; }; + void const_method_1() const {}; + int const_method_2(int, const int&) const { return 4; }; + }; + + void test_method_wrappers() + { + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + static_assert(std::is_same_v), JSNative>); + } + + void test_calling() + { + ScriptInterface script("Test", "Test", g_ScriptContext); + ScriptRequest rq(script); + + ScriptFunction::Register<&TestFunctionWrapper::_1p_v>(script, "_1p_v"); + { + std::string input = "Test._1p_v(0);"; + JS::RootedValue val(rq.cx); + TS_ASSERT(script.Eval(input.c_str(), &val)); + } + + ScriptFunction::Register<&TestFunctionWrapper::_3p_r>(script, "_3p_r"); + { + std::string input = "Test._3p_r(4, false, 'test');"; + int ret = 0; + TS_ASSERT(script.Eval(input.c_str(), ret)); + TS_ASSERT_EQUALS(ret, 4); + } + + ScriptFunction::Register<&TestFunctionWrapper::_cmpt_private_2>(script, "_cmpt_private_2"); + { + std::string input = "Test._cmpt_private_2(4);"; + int ret = 0; + TS_ASSERT(script.Eval(input.c_str(), ret)); + TS_ASSERT_EQUALS(ret, 4); + } + } +}; Index: ps/trunk/source/simulation2/components/tests/test_scripts.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_scripts.h +++ ps/trunk/source/simulation2/components/tests/test_scripts.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* 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 @@ -21,6 +21,7 @@ #include "ps/Filesystem.h" +#include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptContext.h" class TestComponentScripts : public CxxTest::TestSuite @@ -118,9 +119,10 @@ ScriptTestSetup(componentManager.GetScriptInterface()); - componentManager.GetScriptInterface().RegisterFunction ("LoadComponentScript"); - componentManager.GetScriptInterface().RegisterFunction ("LoadHelperScript"); - componentManager.GetScriptInterface().RegisterFunction ("SerializationRoundTrip"); + ScriptRequest rq(componentManager.GetScriptInterface()); + ScriptFunction::Register(rq, "LoadComponentScript"); + ScriptFunction::Register(rq, "LoadHelperScript"); + ScriptFunction::Register(rq, "SerializationRoundTrip"); componentManager.LoadComponentTypes(); Index: ps/trunk/source/simulation2/system/ComponentManager.h =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.h +++ ps/trunk/source/simulation2/system/ComponentManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* 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 @@ -273,22 +273,19 @@ private: // Implementations of functions exposed to scripts - static void Script_RegisterComponentType_Common(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent); - static void Script_RegisterComponentType(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor); - static void Script_RegisterSystemComponentType(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor); - static void Script_ReRegisterComponentType(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor); - static void Script_RegisterInterface(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name); - static void Script_RegisterMessageType(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name); - static void Script_RegisterGlobal(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name, JS::HandleValue value); - static IComponent* Script_QueryInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int ent, int iid); - static std::vector Script_GetEntitiesWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid); - static std::vector Script_GetComponentsWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid); - static void Script_PostMessage(ScriptInterface::CmptPrivate* pCmptPrivate, int ent, int mtid, JS::HandleValue data); - static void Script_BroadcastMessage(ScriptInterface::CmptPrivate* pCmptPrivate, int mtid, JS::HandleValue data); - static int Script_AddEntity(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& templateName); - static int Script_AddLocalEntity(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& templateName); - static void Script_DestroyEntity(ScriptInterface::CmptPrivate* pCmptPrivate, int ent); - static void Script_FlushDestroyedEntities(ScriptInterface::CmptPrivate* pCmptPrivate); + void Script_RegisterComponentType_Common(int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent); + void Script_RegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor); + void Script_RegisterSystemComponentType(int iid, const std::string& cname, JS::HandleValue ctor); + void Script_ReRegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor); + void Script_RegisterInterface(const std::string& name); + void Script_RegisterMessageType(const std::string& name); + void Script_RegisterGlobal(const std::string& name, JS::HandleValue value); + std::vector Script_GetEntitiesWithInterface(int iid); + std::vector Script_GetComponentsWithInterface(int iid); + void Script_PostMessage(int ent, int mtid, JS::HandleValue data); + void Script_BroadcastMessage(int mtid, JS::HandleValue data); + int Script_AddEntity(const std::wstring& templateName); + int Script_AddLocalEntity(const std::wstring& templateName); CMessage* ConstructMessage(int mtid, JS::HandleValue data); void SendGlobalMessage(entity_id_t ent, const CMessage& msg); Index: ps/trunk/source/simulation2/system/ComponentManager.cpp =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.cpp +++ ps/trunk/source/simulation2/system/ComponentManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* 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 @@ -24,6 +24,7 @@ #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_VFS.h" +#include "scriptinterface/FunctionWrapper.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/DynamicSubscription.h" @@ -68,21 +69,23 @@ if (!skipScriptFunctions) { JSI_VFS::RegisterScriptFunctions_Simulation(m_ScriptInterface); - m_ScriptInterface.RegisterFunction ("RegisterComponentType"); - m_ScriptInterface.RegisterFunction ("RegisterSystemComponentType"); - m_ScriptInterface.RegisterFunction ("ReRegisterComponentType"); - m_ScriptInterface.RegisterFunction ("RegisterInterface"); - m_ScriptInterface.RegisterFunction ("RegisterMessageType"); - m_ScriptInterface.RegisterFunction ("RegisterGlobal"); - m_ScriptInterface.RegisterFunction ("QueryInterface"); - m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface"); - m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface"); - m_ScriptInterface.RegisterFunction ("PostMessage"); - m_ScriptInterface.RegisterFunction ("BroadcastMessage"); - m_ScriptInterface.RegisterFunction ("AddEntity"); - m_ScriptInterface.RegisterFunction ("AddLocalEntity"); - m_ScriptInterface.RegisterFunction ("DestroyEntity"); - m_ScriptInterface.RegisterFunction ("FlushDestroyedEntities"); + ScriptRequest rq(m_ScriptInterface); + constexpr ScriptFunction::ObjectGetter Getter = &ScriptFunction::ObjectFromCBData; + ScriptFunction::Register<&CComponentManager::Script_RegisterComponentType, Getter>(rq, "RegisterComponentType"); + ScriptFunction::Register<&CComponentManager::Script_RegisterSystemComponentType, Getter>(rq, "RegisterSystemComponentType"); + ScriptFunction::Register<&CComponentManager::Script_ReRegisterComponentType, Getter>(rq, "ReRegisterComponentType"); + ScriptFunction::Register<&CComponentManager::Script_RegisterInterface, Getter>(rq, "RegisterInterface"); + ScriptFunction::Register<&CComponentManager::Script_RegisterMessageType, Getter>(rq, "RegisterMessageType"); + ScriptFunction::Register<&CComponentManager::Script_RegisterGlobal, Getter>(rq, "RegisterGlobal"); + ScriptFunction::Register<&CComponentManager::Script_GetEntitiesWithInterface, Getter>(rq, "GetEntitiesWithInterface"); + ScriptFunction::Register<&CComponentManager::Script_GetComponentsWithInterface, Getter>(rq, "GetComponentsWithInterface"); + ScriptFunction::Register<&CComponentManager::Script_PostMessage, Getter>(rq, "PostMessage"); + ScriptFunction::Register<&CComponentManager::Script_BroadcastMessage, Getter>(rq, "BroadcastMessage"); + ScriptFunction::Register<&CComponentManager::Script_AddEntity, Getter>(rq, "AddEntity"); + ScriptFunction::Register<&CComponentManager::Script_AddLocalEntity, Getter>(rq, "AddLocalEntity"); + ScriptFunction::Register<&CComponentManager::QueryInterface, Getter>(rq, "QueryInterface"); + ScriptFunction::Register<&CComponentManager::DestroyComponentsSoon, Getter>(rq, "DestroyEntity"); + ScriptFunction::Register<&CComponentManager::FlushDestroyedComponents, Getter>(rq, "FlushDestroyedEntities"); } // Globalscripts may use VFS script functions @@ -149,23 +152,22 @@ return ok; } -void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent) +void CComponentManager::Script_RegisterComponentType_Common(int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - ScriptRequest rq(componentManager->m_ScriptInterface); + ScriptRequest rq(m_ScriptInterface); // Find the C++ component that wraps the interface - int cidWrapper = componentManager->GetScriptWrapper(iid); + int cidWrapper = GetScriptWrapper(iid); if (cidWrapper == CID__Invalid) { ScriptException::Raise(rq, "Invalid interface id"); return; } - const ComponentType& ctWrapper = componentManager->m_ComponentTypesById[cidWrapper]; + const ComponentType& ctWrapper = m_ComponentTypesById[cidWrapper]; bool mustReloadComponents = false; // for hotloading - ComponentTypeId cid = componentManager->LookupCID(cname); + ComponentTypeId cid = LookupCID(cname); if (cid == CID__Invalid) { if (reRegister) @@ -174,22 +176,22 @@ return; } // Allocate a new cid number - cid = componentManager->m_NextScriptComponentTypeId++; - componentManager->m_ComponentTypeIdsByName[cname] = cid; + cid = m_NextScriptComponentTypeId++; + m_ComponentTypeIdsByName[cname] = cid; if (systemComponent) - componentManager->MarkScriptedComponentForSystemEntity(cid); + MarkScriptedComponentForSystemEntity(cid); } else { // Component type is already loaded, so do hotloading: - if (!componentManager->m_CurrentlyHotloading && !reRegister) + if (!m_CurrentlyHotloading && !reRegister) { ScriptException::Raise(rq, "Registering component type with already-registered name '%s'", cname.c_str()); return; } - const ComponentType& ctPrevious = componentManager->m_ComponentTypesById[cid]; + const ComponentType& ctPrevious = m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) @@ -203,7 +205,7 @@ if (ctPrevious.iid != iid) { // ...though it only matters if any components exist with this type - if (!componentManager->m_ComponentsByTypeId[cid].empty()) + if (!m_ComponentsByTypeId[cid].empty()) { ScriptException::Raise(rq, "Hotloading script component type mustn't change interface ID"); return; @@ -212,14 +214,14 @@ // Remove the old component type's message subscriptions std::map >::iterator it; - for (it = componentManager->m_LocalMessageSubscriptions.begin(); it != componentManager->m_LocalMessageSubscriptions.end(); ++it) + for (it = m_LocalMessageSubscriptions.begin(); it != m_LocalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } - for (it = componentManager->m_GlobalMessageSubscriptions.begin(); it != componentManager->m_GlobalMessageSubscriptions.end(); ++it) + for (it = m_GlobalMessageSubscriptions.begin(); it != m_GlobalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); @@ -231,7 +233,7 @@ } JS::RootedValue protoVal(rq.cx); - if (!componentManager->m_ScriptInterface.GetProperty(ctor, "prototype", &protoVal)) + if (!m_ScriptInterface.GetProperty(ctor, "prototype", &protoVal)) { ScriptException::Raise(rq, "Failed to get property 'prototype'"); return; @@ -243,8 +245,8 @@ } std::string schema = ""; - if (componentManager->m_ScriptInterface.HasProperty(protoVal, "Schema")) - componentManager->m_ScriptInterface.GetProperty(protoVal, "Schema", schema); + if (m_ScriptInterface.HasProperty(protoVal, "Schema")) + m_ScriptInterface.GetProperty(protoVal, "Schema", schema); // Construct a new ComponentType, using the wrapper's alloc functions ComponentType ct{ @@ -256,14 +258,14 @@ schema, std::make_unique(rq.cx, ctor) }; - componentManager->m_ComponentTypesById[cid] = std::move(ct); + m_ComponentTypesById[cid] = std::move(ct); - componentManager->m_CurrentComponent = cid; // needed by Subscribe + m_CurrentComponent = cid; // needed by Subscribe // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: std::vector methods; - if (!componentManager->m_ScriptInterface.EnumeratePropertyNames(protoVal, false, methods)) + if (!m_ScriptInterface.EnumeratePropertyNames(protoVal, false, methods)) { ScriptException::Raise(rq, "Failed to enumerate component properties."); return; @@ -285,121 +287,105 @@ name = name.substr(6); } - std::map::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name); - if (mit == componentManager->m_MessageTypeIdsByName.end()) + std::map::const_iterator mit = m_MessageTypeIdsByName.find(name); + if (mit == m_MessageTypeIdsByName.end()) { ScriptException::Raise(rq, "Registered component has unrecognized '%s' message handler method", it->c_str()); return; } if (isGlobal) - componentManager->SubscribeGloballyToMessageType(mit->second); + SubscribeGloballyToMessageType(mit->second); else - componentManager->SubscribeToMessageType(mit->second); + SubscribeToMessageType(mit->second); } - componentManager->m_CurrentComponent = CID__Invalid; + m_CurrentComponent = CID__Invalid; if (mustReloadComponents) { // For every script component with this cid, we need to switch its // prototype from the old constructor's prototype property to the new one's - const std::map& comps = componentManager->m_ComponentsByTypeId[cid]; + const std::map& comps = m_ComponentsByTypeId[cid]; std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { JS::RootedValue instance(rq.cx, eit->second->GetJSInstance()); if (!instance.isNull()) - componentManager->m_ScriptInterface.SetPrototype(instance, protoVal); + m_ScriptInterface.SetPrototype(instance, protoVal); } } } -void CComponentManager::Script_RegisterComponentType(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor) +void CComponentManager::Script_RegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - componentManager->Script_RegisterComponentType_Common(pCmptPrivate, iid, cname, ctor, false, false); - componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); + Script_RegisterComponentType_Common(iid, cname, ctor, false, false); + m_ScriptInterface.SetGlobal(cname.c_str(), ctor, m_CurrentlyHotloading); } -void CComponentManager::Script_RegisterSystemComponentType(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor) +void CComponentManager::Script_RegisterSystemComponentType(int iid, const std::string& cname, JS::HandleValue ctor) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - componentManager->Script_RegisterComponentType_Common(pCmptPrivate, iid, cname, ctor, false, true); - componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); + Script_RegisterComponentType_Common(iid, cname, ctor, false, true); + m_ScriptInterface.SetGlobal(cname.c_str(), ctor, m_CurrentlyHotloading); } -void CComponentManager::Script_ReRegisterComponentType(ScriptInterface::CmptPrivate* pCmptPrivate, int iid, const std::string& cname, JS::HandleValue ctor) +void CComponentManager::Script_ReRegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor) { - Script_RegisterComponentType_Common(pCmptPrivate, iid, cname, ctor, true, false); + Script_RegisterComponentType_Common(iid, cname, ctor, true, false); } -void CComponentManager::Script_RegisterInterface(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name) +void CComponentManager::Script_RegisterInterface(const std::string& name) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - std::map::iterator it = componentManager->m_InterfaceIdsByName.find(name); - if (it != componentManager->m_InterfaceIdsByName.end()) + std::map::iterator it = m_InterfaceIdsByName.find(name); + if (it != m_InterfaceIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported - if (!componentManager->m_CurrentlyHotloading) + if (!m_CurrentlyHotloading) { - ScriptRequest rq(componentManager->m_ScriptInterface); + ScriptRequest rq(m_ScriptInterface); ScriptException::Raise(rq, "Registering interface with already-registered name '%s'", name.c_str()); } return; } // IIDs start at 1, so size+1 is the next unused one - size_t id = componentManager->m_InterfaceIdsByName.size() + 1; - componentManager->m_InterfaceIdsByName[name] = (InterfaceId)id; - componentManager->m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId - componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); + size_t id = m_InterfaceIdsByName.size() + 1; + m_InterfaceIdsByName[name] = (InterfaceId)id; + m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId + m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); } -void CComponentManager::Script_RegisterMessageType(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name) +void CComponentManager::Script_RegisterMessageType(const std::string& name) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - std::map::iterator it = componentManager->m_MessageTypeIdsByName.find(name); - if (it != componentManager->m_MessageTypeIdsByName.end()) + std::map::iterator it = m_MessageTypeIdsByName.find(name); + if (it != m_MessageTypeIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported - if (!componentManager->m_CurrentlyHotloading) + if (!m_CurrentlyHotloading) { - ScriptRequest rq(componentManager->m_ScriptInterface); + ScriptRequest rq(m_ScriptInterface); ScriptException::Raise(rq, "Registering message type with already-registered name '%s'", name.c_str()); } return; } // MTIDs start at 1, so size+1 is the next unused one - size_t id = componentManager->m_MessageTypeIdsByName.size() + 1; - componentManager->RegisterMessageType((MessageTypeId)id, name.c_str()); - componentManager->m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); -} - -void CComponentManager::Script_RegisterGlobal(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name, JS::HandleValue value) -{ - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading); + size_t id = m_MessageTypeIdsByName.size() + 1; + RegisterMessageType((MessageTypeId)id, name.c_str()); + m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); } -IComponent* CComponentManager::Script_QueryInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int ent, int iid) +void CComponentManager::Script_RegisterGlobal(const std::string& name, JS::HandleValue value) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid); - return component; + m_ScriptInterface.SetGlobal(name.c_str(), value, m_CurrentlyHotloading); } -std::vector CComponentManager::Script_GetEntitiesWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid) +std::vector CComponentManager::Script_GetEntitiesWithInterface(int iid) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - std::vector ret; - const InterfaceListUnordered& ents = componentManager->GetEntitiesWithInterfaceUnordered(iid); + const InterfaceListUnordered& ents = GetEntitiesWithInterfaceUnordered(iid); for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) if (!ENTITY_IS_LOCAL(it->first)) ret.push_back(it->first); @@ -407,12 +393,10 @@ return ret; } -std::vector CComponentManager::Script_GetComponentsWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid) +std::vector CComponentManager::Script_GetComponentsWithInterface(int iid) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - std::vector ret; - InterfaceList ents = componentManager->GetEntitiesWithInterface(iid); + InterfaceList ents = GetEntitiesWithInterface(iid); for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) ret.push_back(it->second); // TODO: maybe we should exclude local entities return ret; @@ -433,67 +417,40 @@ } } -void CComponentManager::Script_PostMessage(ScriptInterface::CmptPrivate* pCmptPrivate, int ent, int mtid, JS::HandleValue data) +void CComponentManager::Script_PostMessage(int ent, int mtid, JS::HandleValue data) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - CMessage* msg = componentManager->ConstructMessage(mtid, data); + CMessage* msg = ConstructMessage(mtid, data); if (!msg) return; // error - componentManager->PostMessage(ent, *msg); + PostMessage(ent, *msg); delete msg; } -void CComponentManager::Script_BroadcastMessage(ScriptInterface::CmptPrivate* pCmptPrivate, int mtid, JS::HandleValue data) +void CComponentManager::Script_BroadcastMessage(int mtid, JS::HandleValue data) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - CMessage* msg = componentManager->ConstructMessage(mtid, data); + CMessage* msg = ConstructMessage(mtid, data); if (!msg) return; // error - componentManager->BroadcastMessage(*msg); + BroadcastMessage(*msg); delete msg; } -int CComponentManager::Script_AddEntity(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& templateName) +int CComponentManager::Script_AddEntity(const std::wstring& templateName) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files - - entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewEntity()); - return (int)ent; + return AddEntity(templateName, AllocateNewEntity()); } -int CComponentManager::Script_AddLocalEntity(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& templateName) +int CComponentManager::Script_AddLocalEntity(const std::wstring& templateName) { - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files - - entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewLocalEntity()); - return (int)ent; -} - -void CComponentManager::Script_DestroyEntity(ScriptInterface::CmptPrivate* pCmptPrivate, int ent) -{ - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - - componentManager->DestroyComponentsSoon(ent); -} - -void CComponentManager::Script_FlushDestroyedEntities(ScriptInterface::CmptPrivate *pCmptPrivate) -{ - CComponentManager* componentManager = static_cast (pCmptPrivate->pCBData); - componentManager->FlushDestroyedComponents(); + return AddEntity(templateName, AllocateNewLocalEntity()); } void CComponentManager::ResetState() Index: ps/trunk/source/test_setup.cpp =================================================================== --- ps/trunk/source/test_setup.cpp +++ ps/trunk/source/test_setup.cpp @@ -36,6 +36,7 @@ #include "lib/timer.h" #include "lib/sysdep/sysdep.h" #include "ps/Profiler2.h" +#include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptEngine.h" #include "scriptinterface/ScriptContext.h" #include "scriptinterface/ScriptInterface.h" @@ -138,15 +139,16 @@ namespace { - void script_TS_FAIL(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::wstring& msg) + void script_TS_FAIL(const std::wstring& msg) { TS_FAIL(utf8_from_wstring(msg).c_str()); } } -void ScriptTestSetup(const ScriptInterface& scriptinterface) +void ScriptTestSetup(const ScriptInterface& scriptInterface) { - scriptinterface.RegisterFunction("TS_FAIL"); + ScriptRequest rq(scriptInterface); + ScriptFunction::Register(rq, "TS_FAIL"); // Load the TS_* function definitions // (We don't use VFS because tests might not have the normal VFS paths loaded) @@ -154,5 +156,5 @@ std::ifstream ifs(OsString(path).c_str()); ENSURE(ifs.good()); std::string content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - ENSURE(scriptinterface.LoadScript(L"test_setup.js", content)); + ENSURE(scriptInterface.LoadScript(L"test_setup.js", content)); }