1
0
forked from 0ad/0ad

Reduce duplication in JSI GUI objects implementation.

- Store the functions in an unordered_map. A no-container implementation
is doable but likely not worth the added code complexity, maybe with
C++20
- Move more things into the _impl.h
- Clear out the un-necessary friends declaration in the specific types
by moving the functions to the public interface, which makes sense.
- Fix a memory leak (JS::PersistentRootedObject weren't deleted).

This doesn't change what one needs to do to add a new type, but it does
reduce the actual code that's necessary, and makes it less error prone.

Differential Revision: https://code.wildfiregames.com/D3214
This was SVN commit r24384.
This commit is contained in:
wraitii 2020-12-13 15:06:18 +00:00
parent 876f6d5e50
commit 1b67a079fb
15 changed files with 202 additions and 277 deletions

View File

@ -23,6 +23,7 @@
#include "gui/ObjectTypes/CGUIDummyObject.h" #include "gui/ObjectTypes/CGUIDummyObject.h"
#include "gui/ObjectTypes/CTooltip.h" #include "gui/ObjectTypes/CTooltip.h"
#include "gui/Scripting/ScriptFunctions.h" #include "gui/Scripting/ScriptFunctions.h"
#include "gui/Scripting/JSInterface_GUIProxy.h"
#include "i18n/L10n.h" #include "i18n/L10n.h"
#include "lib/bits.h" #include "lib/bits.h"
#include "lib/input.h" #include "lib/input.h"

View File

@ -47,6 +47,7 @@ struct SGUIImageEffects;
struct SGUIScrollBarStyle; struct SGUIScrollBarStyle;
namespace js { class BaseProxyHandler; } namespace js { class BaseProxyHandler; }
class GUIProxyProps;
using map_pObjects = std::map<CStr, IGUIObject*>; using map_pObjects = std::map<CStr, IGUIObject*>;
@ -247,7 +248,7 @@ public:
*/ */
const CGUIColor& GetPreDefinedColor(const CStr& name) const { return m_PreDefinedColors.at(name); } const CGUIColor& GetPreDefinedColor(const CStr& name) const { return m_PreDefinedColors.at(name); }
void* GetProxyData(const js::BaseProxyHandler* ptr) { return m_ProxyData.at(ptr); } GUIProxyProps* GetProxyData(const js::BaseProxyHandler* ptr) { return m_ProxyData.at(ptr).get(); }
shared_ptr<ScriptInterface> GetScriptInterface() { return m_ScriptInterface; }; shared_ptr<ScriptInterface> GetScriptInterface() { return m_ScriptInterface; };
@ -611,15 +612,16 @@ private:
std::map<CStr, ConstructObjectFunction> m_ObjectTypes; std::map<CStr, ConstructObjectFunction> m_ObjectTypes;
/** /**
* This is intended to store the JS Functions returned when accessing some object properties. * This is intended to store the JSFunction when accessing certain properties.
* It's not a great solution, but I can't find a better one at the moment.
* The problem is that these functions are per-scriptInterface, and proxy handlers aren't. * The problem is that these functions are per-scriptInterface, and proxy handlers aren't.
* So we know what we want to store, but we don't really have anywhere to store it. * So we know what we want to store, but we don't really have anywhere to store it.
* It would be simpler to recreate the functions on every JS call, but that is slower * It would be simpler to recreate the functions on every JS call, but that is slower
* (this may or may not matter now and in the future). * (this may or may not matter now and in the future).
* Another alternative would be to store them on each proxied object, but that wastes memory. * It's not a great solution, but I can't find a better one at the moment.
* An alternative would be to store these on the proxy's prototype,
* but that embarks a lot of un-necessary code.
*/ */
std::unordered_map<const js::BaseProxyHandler*, void*> m_ProxyData; std::unordered_map<const js::BaseProxyHandler*, std::unique_ptr<GUIProxyProps>> m_ProxyData;
/** /**
* Map from hotkey names to objects that listen to the hotkey. * Map from hotkey names to objects that listen to the hotkey.

View File

@ -462,15 +462,8 @@ bool IGUIObject::ScriptEventWithReturn(const CStr& eventName, const JS::HandleVa
void IGUIObject::CreateJSObject() void IGUIObject::CreateJSObject()
{ {
ScriptRequest rq(m_pGUI.GetScriptInterface()); ScriptRequest rq(m_pGUI.GetScriptInterface());
using ProxyHandler = JSI_GUIProxy<std::remove_pointer_t<decltype(this)>>;
js::ProxyOptions options; ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
options.setClass(&JSI_GUIProxy<IGUIObject>::ClassDefinition());
JS::RootedValue cppObj(rq.cx), data(rq.cx);
cppObj.get().setPrivate(this);
data.get().setPrivate(GetGUI().GetProxyData(&JSI_GUIProxy<IGUIObject>::Singleton()));
m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy<IGUIObject>::Singleton(), cppObj, nullptr, options));
js::SetProxyReservedSlot(m_JSObject, 0, data);
} }
JSObject* IGUIObject::GetJSObject() JSObject* IGUIObject::GetJSObject()

View File

@ -119,23 +119,16 @@ const CGUIColor& CButton::ChooseColor()
return m_TextColorOver || m_TextColor; return m_TextColorOver || m_TextColor;
} }
void CButton::CreateJSObject()
{
ScriptRequest rq(m_pGUI.GetScriptInterface());
js::ProxyOptions options;
options.setClass(&JSI_GUIProxy<CButton>::ClassDefinition());
JS::RootedValue cppObj(rq.cx), data(rq.cx);
cppObj.get().setPrivate(this);
data.get().setPrivate(GetGUI().GetProxyData(&JSI_GUIProxy<CButton>::Singleton()));
m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy<CButton>::Singleton(), cppObj, nullptr, options));
js::SetProxyReservedSlot(m_JSObject, 0, data);
}
void CButton::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) void CButton::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{ {
ScriptRequest rq(scriptInterface); ScriptRequest rq(scriptInterface);
UpdateText(); UpdateText();
ScriptInterface::ToJSVal(rq, ret, m_GeneratedTexts[0].GetSize()); ScriptInterface::ToJSVal(rq, ret, m_GeneratedTexts[0].GetSize());
} }
void CButton::CreateJSObject()
{
ScriptRequest rq(m_pGUI.GetScriptInterface());
using ProxyHandler = JSI_GUIProxy<std::remove_pointer_t<decltype(this)>>;
ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
}

View File

@ -27,9 +27,6 @@
class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior
{ {
GUI_OBJECT(CButton) GUI_OBJECT(CButton)
friend JSI_GUIProxy<CButton>;
public: public:
CButton(CGUI& pGUI); CButton(CGUI& pGUI);
virtual ~CButton(); virtual ~CButton();
@ -54,6 +51,11 @@ public:
*/ */
virtual void Draw(); virtual void Draw();
/**
* Populate @param ret with the object's text size.
*/
void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
protected: protected:
/** /**
* Sets up text, should be called every time changes has been * Sets up text, should be called every time changes has been
@ -73,8 +75,6 @@ protected:
virtual void CreateJSObject(); virtual void CreateJSObject();
void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
// Settings // Settings
float m_BufferZone; float m_BufferZone;
i32 m_CellID; i32 m_CellID;

View File

@ -504,14 +504,7 @@ int CList::GetHoveredItem()
void CList::CreateJSObject() void CList::CreateJSObject()
{ {
ScriptRequest rq(m_pGUI.GetScriptInterface()); ScriptRequest rq(m_pGUI.GetScriptInterface());
using ProxyHandler = JSI_GUIProxy<std::remove_pointer_t<decltype(this)>>;
js::ProxyOptions options; ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
options.setClass(&JSI_GUIProxy<CList>::ClassDefinition());
JS::RootedValue cppObj(rq.cx), data(rq.cx);
cppObj.get().setPrivate(this);
data.get().setPrivate(GetGUI().GetProxyData(&JSI_GUIProxy<CList>::Singleton()));
m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy<CList>::Singleton(), cppObj, nullptr, options));
js::SetProxyReservedSlot(m_JSObject, 0, data);
} }

View File

@ -38,9 +38,6 @@
class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner
{ {
GUI_OBJECT(CList) GUI_OBJECT(CList)
friend JSI_GUIProxy<CList>;
public: public:
CList(CGUI& pGUI); CList(CGUI& pGUI);
virtual ~CList(); virtual ~CList();

View File

@ -257,15 +257,8 @@ bool CText::MouseOverIcon()
void CText::CreateJSObject() void CText::CreateJSObject()
{ {
ScriptRequest rq(m_pGUI.GetScriptInterface()); ScriptRequest rq(m_pGUI.GetScriptInterface());
using ProxyHandler = JSI_GUIProxy<std::remove_pointer_t<decltype(this)>>;
js::ProxyOptions options; ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
options.setClass(&JSI_GUIProxy<CText>::ClassDefinition());
JS::RootedValue cppObj(rq.cx), data(rq.cx);
cppObj.get().setPrivate(this);
data.get().setPrivate(GetGUI().GetProxyData(&JSI_GUIProxy<CText>::Singleton()));
m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy<CText>::Singleton(), cppObj, nullptr, options));
js::SetProxyReservedSlot(m_JSObject, 0, data);
} }
void CText::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) void CText::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)

View File

@ -30,9 +30,6 @@
class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner
{ {
GUI_OBJECT(CText) GUI_OBJECT(CText)
friend JSI_GUIProxy<CText>;
public: public:
CText(CGUI& pGUI); CText(CGUI& pGUI);
virtual ~CText(); virtual ~CText();
@ -52,6 +49,10 @@ public:
*/ */
virtual bool MouseOverIcon(); virtual bool MouseOverIcon();
/**
* Populate @param ret with the object's text size.
*/
void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
protected: protected:
/** /**
* Sets up text, should be called every time changes has been * Sets up text, should be called every time changes has been
@ -71,8 +72,6 @@ protected:
virtual void CreateJSObject(); virtual void CreateJSObject();
void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
/** /**
* Placement of text. Ignored when scrollbars are active. * Placement of text. Ignored when scrollbars are active.
*/ */

View File

@ -17,65 +17,22 @@
#include "precompiled.h" #include "precompiled.h"
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectTypes/CButton.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include <string>
// Include the definition of the generic templates.
#include "JSInterface_GUIProxy_impl.h" #include "JSInterface_GUIProxy_impl.h"
namespace #include "gui/ObjectTypes/CButton.h"
{
struct SData
{
JS::PersistentRootedObject m_ToString;
JS::PersistentRootedObject m_Focus;
JS::PersistentRootedObject m_Blur;
JS::PersistentRootedObject m_GetComputedSize;
JS::PersistentRootedObject m_GetTextSize;
};
}
template <> using GUIObjectType = CButton;
bool JSI_GUIProxy<CButton>::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
{
const SData& data = *static_cast<const SData*>(js::GetProxyReservedSlot(proxy, 0).toPrivate());
if (propName == "toString")
return vp.setObjectOrNull(data.m_ToString), true;
if (propName == "focus")
return vp.setObjectOrNull(data.m_Focus), true;
if (propName == "blur")
return vp.setObjectOrNull(data.m_Blur), true;
if (propName == "getComputedSize")
return vp.setObjectOrNull(data.m_GetComputedSize), true;
if (propName == "getTextSize")
return vp.setObjectOrNull(data.m_GetTextSize), true;
return false;
}
template <> template<>
std::pair<const js::BaseProxyHandler*, void*> JSI_GUIProxy<CButton>::CreateData(ScriptInterface& scriptInterface) void JSI_GUIProxy<GUIObjectType>::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
{ {
SData* data = new SData(); #define func(class, func) &JSInterface_GUIProxy::apply_to<GUIObjectType, class, &class::func>
ScriptRequest rq(scriptInterface); cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
#define func(class, func) &apply_to<CButton, class, &class::func> cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); cache->setFunction(rq, "getTextSize", func(GUIObjectType, getTextSize), 0);
data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur")));
data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize")));
data->m_GetTextSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(CButton, getTextSize), 0, 0, "getTextSize")));
#undef func #undef func
return { &Singleton(), data };
} }
template class JSI_GUIProxy<CButton>; template class JSI_GUIProxy<GUIObjectType>;

View File

@ -17,31 +17,9 @@
#include "precompiled.h" #include "precompiled.h"
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectTypes/CList.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include <string>
// Include the definition of the generic templates.
#include "JSInterface_GUIProxy_impl.h" #include "JSInterface_GUIProxy_impl.h"
namespace #include "gui/ObjectTypes/CList.h"
{
struct SData
{
JS::PersistentRootedObject m_ToString;
JS::PersistentRootedObject m_Focus;
JS::PersistentRootedObject m_Blur;
JS::PersistentRootedObject m_GetComputedSize;
JS::PersistentRootedObject m_AddItem;
};
bool CList_AddItem(JSContext* cx, uint argc, JS::Value* vp) bool CList_AddItem(JSContext* cx, uint argc, JS::Value* vp)
{ {
@ -58,40 +36,19 @@ bool CList_AddItem(JSContext* cx, uint argc, JS::Value* vp)
e->AddItem(text, text); e->AddItem(text, text);
return true; return true;
} }
}
template <> using GUIObjectType = CList;
bool JSI_GUIProxy<CList>::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
template<>
void JSI_GUIProxy<GUIObjectType>::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
{ {
const SData& data = *static_cast<const SData*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()); #define func(class, func) &JSInterface_GUIProxy::apply_to<GUIObjectType, class, &class::func>
if (propName == "toString") cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
return vp.setObjectOrNull(data.m_ToString), true; cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
if (propName == "focus") cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
return vp.setObjectOrNull(data.m_Focus), true; cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
if (propName == "blur")
return vp.setObjectOrNull(data.m_Blur), true;
if (propName == "getComputedSize")
return vp.setObjectOrNull(data.m_GetComputedSize), true;
if (propName == "addItem")
return vp.setObjectOrNull(data.m_AddItem), true;
return false;
}
template <>
std::pair<const js::BaseProxyHandler*, void*> JSI_GUIProxy<CList>::CreateData(ScriptInterface& scriptInterface)
{
SData* data = new SData();
ScriptRequest rq(scriptInterface);
#define func(class, func) &apply_to<CList, class, &class::func>
data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString")));
data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus")));
data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur")));
data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize")));
#undef func #undef func
data->m_AddItem.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, CList_AddItem, 1, 0, "addItem"))); cache->setFunction(rq, "addItem", CList_AddItem, 1);
return { &Singleton(), data };
} }
template class JSI_GUIProxy<CList>; template class JSI_GUIProxy<GUIObjectType>;

View File

@ -17,65 +17,22 @@
#include "precompiled.h" #include "precompiled.h"
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectTypes/CText.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include <string>
// Include the definition of the generic templates.
#include "JSInterface_GUIProxy_impl.h" #include "JSInterface_GUIProxy_impl.h"
namespace #include "gui/ObjectTypes/CText.h"
{
struct SData
{
JS::PersistentRootedObject m_ToString;
JS::PersistentRootedObject m_Focus;
JS::PersistentRootedObject m_Blur;
JS::PersistentRootedObject m_GetComputedSize;
JS::PersistentRootedObject m_GetTextSize;
};
}
template <> using GUIObjectType = CText;
bool JSI_GUIProxy<CText>::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
{
const SData& data = *static_cast<const SData*>(js::GetProxyReservedSlot(proxy, 0).toPrivate());
if (propName == "toString")
return vp.setObjectOrNull(data.m_ToString), true;
if (propName == "focus")
return vp.setObjectOrNull(data.m_Focus), true;
if (propName == "blur")
return vp.setObjectOrNull(data.m_Blur), true;
if (propName == "getComputedSize")
return vp.setObjectOrNull(data.m_GetComputedSize), true;
if (propName == "getTextSize")
return vp.setObjectOrNull(data.m_GetTextSize), true;
return false;
}
template <> template<>
std::pair<const js::BaseProxyHandler*, void*> JSI_GUIProxy<CText>::CreateData(ScriptInterface& scriptInterface) void JSI_GUIProxy<GUIObjectType>::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
{ {
SData* data = new SData(); #define func(class, func) &JSInterface_GUIProxy::apply_to<GUIObjectType, class, &class::func>
ScriptRequest rq(scriptInterface); cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
#define func(class, func) &apply_to<CText, class, &class::func> cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); cache->setFunction(rq, "getTextSize", func(GUIObjectType, getTextSize), 0);
data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur")));
data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize")));
data->m_GetTextSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(CText, getTextSize), 0, 0, "getTextSize")));
#undef func #undef func
return { &Singleton(), data };
} }
template class JSI_GUIProxy<CText>; template class JSI_GUIProxy<GUIObjectType>;

View File

@ -23,6 +23,7 @@
#include <utility> #include <utility>
class ScriptInterface; class ScriptInterface;
class ScriptRequest;
// See JSI_GuiProxy below // See JSI_GuiProxy below
#if GCC_VERSION #if GCC_VERSION
@ -33,6 +34,22 @@ class ScriptInterface;
# pragma warning(disable: 4265) # pragma warning(disable: 4265)
#endif #endif
/**
* Proxies need to store some data whose lifetime is tied to an interface.
* This is the virtual interface of that data.
*/
class GUIProxyProps
{
public:
virtual ~GUIProxyProps() {};
// @return true if @param name exists in this cache.
virtual bool has(const std::string& name) const = 0;
// @return the JSFunction matching @param name. Must call has() first as it can assume existence.
virtual JSObject* get(const std::string& name) const = 0;
virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSNative function, int nargs) = 0;
};
/** /**
* Handles the js interface with C++ GUI objects. * Handles the js interface with C++ GUI objects.
* Proxy handlers must live for at least as long as the JS runtime * Proxy handlers must live for at least as long as the JS runtime
@ -43,6 +60,17 @@ class ScriptInterface;
* GUI Objects only exist in C++ and have no JS-only properties. * GUI Objects only exist in C++ and have no JS-only properties.
* As such, there is no "target" JS object that this proxy could point to, * As such, there is no "target" JS object that this proxy could point to,
* and thus we should inherit from BaseProxyHandler and not js::Wrapper. * and thus we should inherit from BaseProxyHandler and not js::Wrapper.
*
* Proxies can be used with prototypes and almost treated like regular JS objects.
* However, the default implementation embarks a lot of code that we don't really need here,
* since the only fanciness is that we cache JSFunction* properties.
* As such, these GUI proxies don't have one and instead override get/set directly.
*
* To add a new JSI_GUIProxy, you'll need to:
* - overload the GUI Object's CreateJSObject with the correct types and pointers
* - change the CGUI::AddObjectTypes method
* - explicitly instantiate the template.
*
*/ */
template<typename GUIObjectType> template<typename GUIObjectType>
class JSI_GUIProxy : public js::BaseProxyHandler class JSI_GUIProxy : public js::BaseProxyHandler
@ -54,17 +82,27 @@ public:
// For convenience, this is the single instantiated JSI_GUIProxy. // For convenience, this is the single instantiated JSI_GUIProxy.
static JSI_GUIProxy& Singleton(); static JSI_GUIProxy& Singleton();
static std::pair<const js::BaseProxyHandler*, void*> CreateData(ScriptInterface& scriptInterface); // Call this in CGUI::AddObjectTypes.
static std::pair<const js::BaseProxyHandler*, GUIProxyProps*> CreateData(ScriptInterface& scriptInterface);
static void CreateJSObject(const ScriptRequest& rq, GUIObjectType* ptr, GUIProxyProps* data, JS::PersistentRootedObject& val);
protected: protected:
// @param family can't be nullptr because that's used for some DOM object and it crashes. // @param family can't be nullptr because that's used for some DOM object and it crashes.
JSI_GUIProxy() : BaseProxyHandler(this, false, false) {}; JSI_GUIProxy() : BaseProxyHandler(this, false, false) {};
// Note: SM provides no virtual destructor for baseProxyHandler. // Note: SM provides no virtual destructor for baseProxyHandler.
// This also enforces making proxy handlers dataless static variables. // This also enforces making proxy handlers dataless static variables.
~JSI_GUIProxy() {}; ~JSI_GUIProxy() {};
// This handles returning function properties. // The default implementations need to know the type of the GUIProxyProps for this proxy type.
// Specialize this. // This is done by specializing this struct's alias type.
bool FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const; struct PropCache;
// Specialize this to define the custom properties of this type.
static void CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache);
// This handles returning custom properties. Specialize this if needed.
bool PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const;
protected: protected:
// BaseProxyHandler interface below // BaseProxyHandler interface below

View File

@ -17,6 +17,17 @@
// This file is included directly into actual implementation files. // This file is included directly into actual implementation files.
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include <string>
template <typename T> template <typename T>
JSClass& JSI_GUIProxy<T>::ClassDefinition() JSClass& JSI_GUIProxy<T>::ClassDefinition()
{ {
@ -31,8 +42,12 @@ JSI_GUIProxy<T>& JSI_GUIProxy<T>::Singleton()
return s; return s;
} }
namespace // Use a common namespace to avoid duplicating the symbols un-necessarily.
namespace JSInterface_GUIProxy
{ {
/**
* Conveniently wrap a simple C++ function to a JSNative.
*/
template<class OG, class R, void (R::*funcptr)(ScriptInterface&, JS::MutableHandleValue)> template<class OG, class R, void (R::*funcptr)(ScriptInterface&, JS::MutableHandleValue)>
inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp) inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp)
{ {
@ -45,6 +60,76 @@ inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp)
return true; return true;
} }
// Default implementation of the cache via unordered_map
class MapCache : public GUIProxyProps
{
public:
virtual ~MapCache() {};
virtual bool has(const std::string& name) const override
{
return m_Functions.find(name) != m_Functions.end();
}
virtual JSObject* get(const std::string& name) const override
{
return m_Functions.at(name).get();
}
virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSNative function, int nargs) override
{
m_Functions[name].init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, function, nargs, 0, name.c_str())));
return true;
}
protected:
std::unordered_map<std::string, JS::PersistentRootedObject> m_Functions;
};
}
// The default propcache is a MapCache.
template<typename T>
struct JSI_GUIProxy<T>::PropCache
{
using type = JSInterface_GUIProxy::MapCache;
};
template <typename T>
bool JSI_GUIProxy<T>::PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
{
using PropertyCache = typename PropCache::type;
// Since we know at compile time what the type actually is, avoid the virtual call.
const PropertyCache* data = static_cast<const PropertyCache*>(static_cast<const GUIProxyProps*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
if (data->has(propName))
{
vp.setObjectOrNull(data->get(propName));
return true;
}
return false;
}
template <typename T>
std::pair<const js::BaseProxyHandler*, GUIProxyProps*> JSI_GUIProxy<T>::CreateData(ScriptInterface& scriptInterface)
{
using PropertyCache = typename PropCache::type;
PropertyCache* data = new PropertyCache();
ScriptRequest rq(scriptInterface);
CreateFunctions(rq, data);
return { &Singleton(), data };
}
template<typename T>
void JSI_GUIProxy<T>::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr, JS::PersistentRootedObject& val)
{
js::ProxyOptions options;
options.setClass(&ClassDefinition());
JS::RootedValue cppObj(rq.cx), data(rq.cx);
cppObj.get().setPrivate(ptr);
data.get().setPrivate(static_cast<void*>(dataPtr));
val.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
js::SetProxyReservedSlot(val, 0, data);
} }
template <typename T> template <typename T>
@ -66,7 +151,7 @@ bool JSI_GUIProxy<T>::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue
return false; return false;
// Return function properties. Specializable. // Return function properties. Specializable.
if (FuncGetter(proxy, propName, vp)) if (PropGetter(proxy, propName, vp))
return true; return true;
// Use onWhatever to access event handlers // Use onWhatever to access event handlers

View File

@ -17,59 +17,19 @@
#include "precompiled.h" #include "precompiled.h"
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectTypes/CText.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include <string>
// Include the definition of the generic templates.
#include "JSInterface_GUIProxy_impl.h" #include "JSInterface_GUIProxy_impl.h"
namespace using GUIObjectType = IGUIObject;
{
struct SData
{
JS::PersistentRootedObject m_ToString;
JS::PersistentRootedObject m_Focus;
JS::PersistentRootedObject m_Blur;
JS::PersistentRootedObject m_GetComputedSize;
};
}
template <> template<>
bool JSI_GUIProxy<IGUIObject>::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const void JSI_GUIProxy<GUIObjectType>::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
{ {
const SData& data = *static_cast<const SData*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()); #define func(class, func) &JSInterface_GUIProxy::apply_to<GUIObjectType, class, &class::func>
if (propName == "toString") cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
return vp.setObjectOrNull(data.m_ToString), true; cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
if (propName == "focus") cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
return vp.setObjectOrNull(data.m_Focus), true; cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
if (propName == "blur")
return vp.setObjectOrNull(data.m_Blur), true;
if (propName == "getComputedSize")
return vp.setObjectOrNull(data.m_GetComputedSize), true;
return false;
}
template <>
std::pair<const js::BaseProxyHandler*, void*> JSI_GUIProxy<IGUIObject>::CreateData(ScriptInterface& scriptInterface)
{
SData* data = new SData();
ScriptRequest rq(scriptInterface);
#define func(class, func) &apply_to<IGUIObject, class, &class::func>
data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString")));
data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus")));
data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur")));
data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize")));
#undef func #undef func
return { &Singleton(), data };
} }
template class JSI_GUIProxy<IGUIObject>; template class JSI_GUIProxy<GUIObjectType>;