1
0
forked from 0ad/0ad

Introduce C++ templates replacements for DEFINE_INTERFACE_X and RegisterFunction macros

The new methods:
- aren't included in ScriptInterface.h directly, lightening that header
- don't use boost CPP
- don't need argument types or number or constness to be specified
- can work with object methods somewhat transparently
- support optional cmptPrivate (allowing removal of many UNUSED macro)
- support optional const ScriptRequest&, which is safer.

This first diff changes only some of the JSI files & the component
manager. Further diffs will update other files and finally delete the
current code.

Differential Revision: https://code.wildfiregames.com/D2818
This was SVN commit r24969.
This commit is contained in:
wraitii 2021-03-01 20:52:24 +00:00
parent 9ed3b88d25
commit f3aedf88a6
17 changed files with 562 additions and 243 deletions

View File

@ -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);

View File

@ -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 @@ public:
const wchar_t* GetBuffer();
void FlushBuffer();
bool IsActive() { return m_bVisible; }
bool IsActive() const { return m_bVisible; }
int m_iFontHeight;
int m_iFontWidth;

View File

@ -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 @@ void RunHardwareDetection()
JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
scriptInterface.RegisterFunction<void, bool, &SetDisableAudio>("SetDisableAudio");
ScriptFunction::Register<SetDisableAudio>(rq, "SetDisableAudio");
// Load the detection script:

View File

@ -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 g_Console;
}
return true;
}
bool JSI_Console::GetVisibleEnabled(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate))
void JSI_Console::RegisterScriptFunctions(const ScriptRequest& rq)
{
if (!CheckGlobalInitialized())
return false;
return g_Console->IsActive();
}
void JSI_Console::SetVisibleEnabled(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), bool Enabled)
{
if (!CheckGlobalInitialized())
return;
g_Console->SetVisible(Enabled);
}
void JSI_Console::RegisterScriptFunctions(const ScriptInterface& scriptInterface)
{
scriptInterface.RegisterFunction<bool, &JSI_Console::GetVisibleEnabled>("Console_GetVisibleEnabled");
scriptInterface.RegisterFunction<void, bool, &JSI_Console::SetVisibleEnabled>("Console_SetVisibleEnabled");
ScriptFunction::Register<&CConsole::IsActive, ConsoleGetter>(rq, "Console_GetVisibleEnabled");
ScriptFunction::Register<&CConsole::SetVisible, ConsoleGetter>(rq, "Console_SetVisibleEnabled");
}

View File

@ -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

View File

@ -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 <unordered_map>
@ -66,13 +67,13 @@ void ScriptInterface::ToJSVal<std::unordered_map<std::string, std::string>>(cons
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<std::string, std::vector<std::vector<std::string>>> hotkeys;
@ -89,7 +90,7 @@ JS::Value GetHotkeyMap(ScriptInterface::CmptPrivate* pCmptPrivate)
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 @@ JS::Value GetHotkeyMap(ScriptInterface::CmptPrivate* pCmptPrivate)
/**
* @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<std::string, std::string> map;
@ -108,24 +107,21 @@ JS::Value GetScancodeKeyNames(ScriptInterface::CmptPrivate* pCmptPrivate)
// This is slightly wasteful but should be fine overall, they are dense.
for (int i = 0; i < MOUSE_LAST; ++i)
map[FindScancodeName(static_cast<SDL_Scancode>(i))] = FindKeyName(static_cast<SDL_Scancode>(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<std::string> keys;
if (!scriptInterface->FromJSVal(rq, combination, keys))
if (!ScriptInterface::FromJSVal(rq, combination, keys))
{
LOGERROR("Invalid hotkey combination");
return JS::NullValue();
@ -161,14 +157,15 @@ JS::Value GetConflicts(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleVal
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)
{
scriptInterface.RegisterFunction<JS::Value, &GetHotkeyMap>("GetHotkeyMap");
scriptInterface.RegisterFunction<JS::Value, &GetScancodeKeyNames>("GetScancodeKeyNames");
scriptInterface.RegisterFunction<void, &ReloadHotkeys>("ReloadHotkeys");
scriptInterface.RegisterFunction<JS::Value, JS::HandleValue, &GetConflicts>("GetConflicts");
}
void JSI_Hotkey::RegisterScriptFunctions(const ScriptRequest& rq)
{
ScriptFunction::Register<&GetHotkeyMap>(rq, "GetHotkeyMap");
ScriptFunction::Register<&GetScancodeKeyNames>(rq, "GetScancodeKeyNames");
ScriptFunction::Register<&ReloadHotkeys>(rq, "ReloadHotkeys");
ScriptFunction::Register<&GetConflicts>(rq, "GetConflicts");
}

View File

@ -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

View File

@ -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 @@ JS::Value JSI_Mod::GetEngineInfo(ScriptInterface::CmptPrivate* pCmptPrivate)
* @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<CStr>& mods)
void SetMods(const std::vector<CStr>& mods)
{
g_modsLoaded = mods;
}
void JSI_Mod::RegisterScriptFunctions(const ScriptInterface& scriptInterface)
{
scriptInterface.RegisterFunction<JS::Value, &GetEngineInfo>("GetEngineInfo");
scriptInterface.RegisterFunction<JS::Value, &JSI_Mod::GetAvailableMods>("GetAvailableMods");
scriptInterface.RegisterFunction<void, &JSI_Mod::RestartEngine>("RestartEngine");
scriptInterface.RegisterFunction<void, std::vector<CStr>, &JSI_Mod::SetMods>("SetMods");
}
void JSI_Mod::RegisterScriptFunctions(const ScriptRequest& rq)
{
ScriptFunction::Register<&GetEngineInfo>(rq, "GetEngineInfo");
ScriptFunction::Register<&GetAvailableMods>(rq, "GetAvailableMods");
ScriptFunction::Register<&RestartEngine>(rq, "RestartEngine");
ScriptFunction::Register<&SetMods>(rq, "SetMods");
}

View File

@ -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<CStr>& mods);
void RegisterScriptFunctions(const ScriptRequest& rq);
}
#endif // INCLUDED_JSI_MOD

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<typename T>
using type_transform = std::conditional_t<std::is_same_v<const ScriptRequest&, T>, const ScriptRequest&,
std::remove_const_t<typename std::remove_reference_t<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 <class T> struct args_info;
template<typename R, typename ...Types>
struct args_info<R(*)(Types ...)>
{
static constexpr const size_t nb_args = sizeof...(Types);
using return_type = R;
using object_type = void;
using arg_types = std::tuple<type_transform<Types>...>;
};
template<typename C, typename R, typename ...Types>
struct args_info<R(C::*)(Types ...)> : public args_info<R(*)(Types ...)> { using object_type = C; };
template<typename C, typename R, typename ...Types>
struct args_info<R(C::*)(Types ...) const> : public args_info<R(C::*)(Types ...)> {};
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* 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<size_t idx, typename T>
static std::tuple<T> DoConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok)
{
// No need to convert JS values.
if constexpr (std::is_same_v<T, JS::HandleValue>)
{
// 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<T>(rq, args[idx], ret);
return std::forward_as_tuple(ret);
}
}
}
/**
* Recursive wrapper: calls DoConvertFromJS for type T and recurses.
*/
template<size_t idx, typename T, typename V, typename ...Types>
static std::tuple<T, V, Types...> DoConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok)
{
return std::tuple_cat(DoConvertFromJS<idx, T>(rq, args, went_ok), DoConvertFromJS<idx + 1, V, Types...>(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<typename ...Types>
static std::tuple<Types...> ConvertFromJS(ScriptInterface::CmptPrivate*, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple<Types...>*)
{
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<typename ...Types>
static std::tuple<ScriptInterface::CmptPrivate*, Types...> ConvertFromJS(ScriptInterface::CmptPrivate* cmptPrivate, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple<ScriptInterface::CmptPrivate*, Types...>*)
{
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<typename ...Types>
static std::tuple<const ScriptRequest&, Types...> ConvertFromJS(ScriptInterface::CmptPrivate*, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple<const ScriptRequest&, Types...>*)
{
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 <auto callable, typename T, typename tuple>
static typename args_info<decltype(callable)>::return_type call(T* object, tuple& args)
{
if constexpr(std::is_same_v<T, void>)
{
// 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 <typename T>
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 <class callableType>
using GetterFor = ObjectGetter<typename args_info<callableType>::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 <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr>
static bool ToJSNative(JSContext* cx, unsigned argc, JS::Value* vp)
{
using ObjType = typename args_info<decltype(callable)>::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<decltype(callable)>::arg_types outs = ConvertFromJS(ScriptInterface::GetScriptInterfaceAndCBData(cx), rq, args, went_ok, static_cast<typename args_info<decltype(callable)>::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<void, typename args_info<decltype(callable)>::return_type>)
call<callable>(obj, outs);
else if constexpr (std::is_same_v<JS::Value, typename args_info<decltype(callable)>::return_type>)
args.rval().set(call<callable>(obj, outs));
else
ScriptInterface::ToJSVal(rq, args.rval(), call<callable>(obj, outs));
return !ScriptException::IsPending(rq);
}
/**
* Return a function spec from a C++ function.
*/
template <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static JSFunctionSpec Wrap(const char* name)
{
return JS_FN(name, (&ToJSNative<callable>), args_info<decltype(callable)>::nb_args, flags);
}
/**
* Register a function on the native scope (usually 'Engine').
*/
template <auto callable, GetterFor<decltype(callable)> 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<callable, thisGetter>, args_info<decltype(callable)>::nb_args, flags);
}
/**
* Convert the CmptPrivate callback data to T*
*/
template <typename T>
static T* ObjectFromCBData(const ScriptRequest& rq, JS::CallArgs&)
{
return static_cast<T*>(ScriptInterface::GetScriptInterfaceAndCBData(rq.cx)->pCBData);
}
};
#endif // INCLUDED_FUNCTIONWRAPPER

View File

@ -72,7 +72,7 @@ struct ScriptInterface_impl
};
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);

View File

@ -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 @@ public:
JS::Value globalValue() const;
JSContext* cx;
JSObject* glob;
JS::HandleObject nativeScope;
private:
JS::Realm* m_formerRealm;
};

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_1p_v>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_3p_v>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_3p_r>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_0p_v>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_0p_r>), 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<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_handle>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_handle_2>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_cmpt_private>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_cmpt_private_2>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_script_request>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::_script_request_2>), 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<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::test_method::method_1>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::test_method::method_2>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::test_method::const_method_1>), JSNative>);
static_assert(std::is_same_v<decltype(&ScriptFunction::ToJSNative<&TestFunctionWrapper::test_method::const_method_2>), 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);
}
}
};

View File

@ -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 @@ public:
ScriptTestSetup(componentManager.GetScriptInterface());
componentManager.GetScriptInterface().RegisterFunction<void, VfsPath, Script_LoadComponentScript> ("LoadComponentScript");
componentManager.GetScriptInterface().RegisterFunction<void, VfsPath, Script_LoadHelperScript> ("LoadHelperScript");
componentManager.GetScriptInterface().RegisterFunction<JS::Value, JS::HandleValue, Script_SerializationRoundTrip> ("SerializationRoundTrip");
ScriptRequest rq(componentManager.GetScriptInterface());
ScriptFunction::Register<Script_LoadComponentScript>(rq, "LoadComponentScript");
ScriptFunction::Register<Script_LoadHelperScript>(rq, "LoadHelperScript");
ScriptFunction::Register<Script_SerializationRoundTrip>(rq, "SerializationRoundTrip");
componentManager.LoadComponentTypes();

View File

@ -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 @@ CComponentManager::CComponentManager(CSimContext& context, shared_ptr<ScriptCont
if (!skipScriptFunctions)
{
JSI_VFS::RegisterScriptFunctions_Simulation(m_ScriptInterface);
m_ScriptInterface.RegisterFunction<void, int, std::string, JS::HandleValue, CComponentManager::Script_RegisterComponentType> ("RegisterComponentType");
m_ScriptInterface.RegisterFunction<void, int, std::string, JS::HandleValue, CComponentManager::Script_RegisterSystemComponentType> ("RegisterSystemComponentType");
m_ScriptInterface.RegisterFunction<void, int, std::string, JS::HandleValue, CComponentManager::Script_ReRegisterComponentType> ("ReRegisterComponentType");
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterInterface> ("RegisterInterface");
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterMessageType> ("RegisterMessageType");
m_ScriptInterface.RegisterFunction<void, std::string, JS::HandleValue, CComponentManager::Script_RegisterGlobal> ("RegisterGlobal");
m_ScriptInterface.RegisterFunction<IComponent*, int, int, CComponentManager::Script_QueryInterface> ("QueryInterface");
m_ScriptInterface.RegisterFunction<std::vector<int>, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface");
m_ScriptInterface.RegisterFunction<std::vector<IComponent*>, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface");
m_ScriptInterface.RegisterFunction<void, int, int, JS::HandleValue, CComponentManager::Script_PostMessage> ("PostMessage");
m_ScriptInterface.RegisterFunction<void, int, JS::HandleValue, CComponentManager::Script_BroadcastMessage> ("BroadcastMessage");
m_ScriptInterface.RegisterFunction<int, std::string, CComponentManager::Script_AddEntity> ("AddEntity");
m_ScriptInterface.RegisterFunction<int, std::string, CComponentManager::Script_AddLocalEntity> ("AddLocalEntity");
m_ScriptInterface.RegisterFunction<void, int, CComponentManager::Script_DestroyEntity> ("DestroyEntity");
m_ScriptInterface.RegisterFunction<void, CComponentManager::Script_FlushDestroyedEntities> ("FlushDestroyedEntities");
ScriptRequest rq(m_ScriptInterface);
constexpr ScriptFunction::ObjectGetter<CComponentManager> Getter = &ScriptFunction::ObjectFromCBData<CComponentManager>;
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 @@ bool CComponentManager::LoadScript(const VfsPath& filename, bool hotload)
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<CComponentManager*> (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 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
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 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
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 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
// Remove the old component type's message subscriptions
std::map<MessageTypeId, std::vector<ComponentTypeId> >::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<ComponentTypeId>& types = it->second;
std::vector<ComponentTypeId>::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<ComponentTypeId>& types = it->second;
std::vector<ComponentTypeId>::iterator ctit = find(types.begin(), types.end(), cid);
@ -231,7 +233,7 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
}
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 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
}
std::string schema = "<empty/>";
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 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
schema,
std::make_unique<JS::PersistentRootedValue>(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<std::string> 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 @@ void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::Cmp
name = name.substr(6);
}
std::map<std::string, MessageTypeId>::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name);
if (mit == componentManager->m_MessageTypeIdsByName.end())
std::map<std::string, MessageTypeId>::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<entity_id_t, IComponent*>& comps = componentManager->m_ComponentsByTypeId[cid];
const std::map<entity_id_t, IComponent*>& comps = m_ComponentsByTypeId[cid];
std::map<entity_id_t, IComponent*>::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<CComponentManager*> (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<CComponentManager*> (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<CComponentManager*> (pCmptPrivate->pCBData);
std::map<std::string, InterfaceId>::iterator it = componentManager->m_InterfaceIdsByName.find(name);
if (it != componentManager->m_InterfaceIdsByName.end())
std::map<std::string, InterfaceId>::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<CComponentManager*> (pCmptPrivate->pCBData);
std::map<std::string, MessageTypeId>::iterator it = componentManager->m_MessageTypeIdsByName.find(name);
if (it != componentManager->m_MessageTypeIdsByName.end())
std::map<std::string, MessageTypeId>::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);
size_t id = m_MessageTypeIdsByName.size() + 1;
RegisterMessageType((MessageTypeId)id, name.c_str());
m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id);
}
void CComponentManager::Script_RegisterGlobal(ScriptInterface::CmptPrivate* pCmptPrivate, const std::string& name, JS::HandleValue value)
void CComponentManager::Script_RegisterGlobal(const std::string& name, JS::HandleValue value)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (pCmptPrivate->pCBData);
componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading);
m_ScriptInterface.SetGlobal(name.c_str(), value, m_CurrentlyHotloading);
}
IComponent* CComponentManager::Script_QueryInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int ent, int iid)
std::vector<int> CComponentManager::Script_GetEntitiesWithInterface(int iid)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (pCmptPrivate->pCBData);
IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid);
return component;
}
std::vector<int> CComponentManager::Script_GetEntitiesWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (pCmptPrivate->pCBData);
std::vector<int> 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 @@ std::vector<int> CComponentManager::Script_GetEntitiesWithInterface(ScriptInterf
return ret;
}
std::vector<IComponent*> CComponentManager::Script_GetComponentsWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid)
std::vector<IComponent*> CComponentManager::Script_GetComponentsWithInterface(int iid)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (pCmptPrivate->pCBData);
std::vector<IComponent*> 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 @@ CMessage* CComponentManager::ConstructMessage(int mtid, JS::HandleValue data)
}
}
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<CComponentManager*> (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<CComponentManager*> (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<CComponentManager*> (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<CComponentManager*> (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<CComponentManager*> (pCmptPrivate->pCBData);
componentManager->DestroyComponentsSoon(ent);
}
void CComponentManager::Script_FlushDestroyedEntities(ScriptInterface::CmptPrivate *pCmptPrivate)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (pCmptPrivate->pCBData);
componentManager->FlushDestroyedComponents();
return AddEntity(templateName, AllocateNewLocalEntity());
}
void CComponentManager::ResetState()

View File

@ -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 @@ public:
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<int> Script_GetEntitiesWithInterface(ScriptInterface::CmptPrivate* pCmptPrivate, int iid);
static std::vector<IComponent*> 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<int> Script_GetEntitiesWithInterface(int iid);
std::vector<IComponent*> 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);

View File

@ -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 @@ OsPath DataDir()
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<void, std::wstring, script_TS_FAIL>("TS_FAIL");
ScriptRequest rq(scriptInterface);
ScriptFunction::Register<script_TS_FAIL>(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 @@ void ScriptTestSetup(const ScriptInterface& scriptinterface)
std::ifstream ifs(OsString(path).c_str());
ENSURE(ifs.good());
std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ENSURE(scriptinterface.LoadScript(L"test_setup.js", content));
ENSURE(scriptInterface.LoadScript(L"test_setup.js", content));
}