Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
JSInterface_GUIProxy_impl.h
Go to the documentation of this file.
1/* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18// This file is included directly into actual implementation files.
19
21
22#include "gui/CGUI.h"
23#include "gui/CGUISetting.h"
25#include "ps/CLogger.h"
30
31#include <string>
32#include <string_view>
33
34template <typename T>
36{
37 static JSI_GUIProxy<T> s;
38 return s;
39}
40
41// Call this for every specialised type. You will need to override IGUIObject::CreateJSObject() in your class interface.
42#define DECLARE_GUIPROXY(Type) \
43void Type::CreateJSObject() \
44{ \
45 ScriptRequest rq(m_pGUI.GetScriptInterface()); \
46 using ProxyHandler = JSI_GUIProxy<std::remove_pointer_t<decltype(this)>>; \
47 m_JSObject = ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton())); \
48} \
49template class JSI_GUIProxy<Type>;
50
51// Use a common namespace to avoid duplicating the symbols un-necessarily.
53{
54// All proxy objects share a class definition.
56{
57 static JSClass c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(1));
58 return c;
59}
60
61// Default implementation of the cache via unordered_map
62class MapCache : public GUIProxyProps
63{
64public:
65 virtual ~MapCache() {};
66
67 virtual bool has(const std::string& name) const override
68 {
69 return m_Functions.find(name) != m_Functions.end();
70 }
71
72 virtual JSObject* get(const std::string& name) const override
73 {
74 return m_Functions.at(name).get();
75 }
76
77 virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
78 {
79 m_Functions[name].init(rq.cx, JS_GetFunctionObject(function));
80 return true;
81 }
82
83protected:
84 std::unordered_map<std::string, JS::PersistentRootedObject> m_Functions;
85};
86}
87
88template<>
89IGUIObject* IGUIProxyObject::FromPrivateSlot<IGUIObject>(JSObject* obj)
90{
91 if (!obj)
92 return nullptr;
93 if (JS::GetClass(obj) != &JSInterface_GUIProxy::ClassDefinition())
94 return nullptr;
95 return UnsafeFromPrivateSlot<IGUIObject>(obj);
96}
97
98// The default propcache is a MapCache.
99template<typename T>
101{
103};
104
105template <typename T>
107{
108 // Call the unsafe version - this is only ever called from actual proxy objects.
109 return IGUIProxyObject::UnsafeFromPrivateSlot<T>(args.thisv().toObjectOrNull());
110}
111
112template <typename T>
113bool JSI_GUIProxy<T>::PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
114{
115 using PropertyCache = typename PropCache::type;
116 // Since we know at compile time what the type actually is, avoid the virtual call.
117 const PropertyCache* data = static_cast<const PropertyCache*>(static_cast<const GUIProxyProps*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
118 if (data->has(propName))
119 {
120 vp.setObjectOrNull(data->get(propName));
121 return true;
122 }
123 return false;
124}
125
126template <typename T>
127std::pair<const js::BaseProxyHandler*, GUIProxyProps*> JSI_GUIProxy<T>::CreateData(ScriptInterface& scriptInterface)
128{
129 using PropertyCache = typename PropCache::type;
130 PropertyCache* data = new PropertyCache();
131 ScriptRequest rq(scriptInterface);
132
133 // Functions common to all children of IGUIObject.
135
136 // Let derived classes register their own interface.
137 if constexpr (!std::is_same_v<T, IGUIObject>)
138 CreateFunctions(rq, data);
139 return { &Singleton(), data };
140}
141
142template<typename T>
143template<auto callable>
144void JSI_GUIProxy<T>::CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name)
145{
146 cache->setFunction(rq, name, ScriptFunction::Create<callable, FromPrivateSlot>(rq, name.c_str()));
147}
148
149template<typename T>
150std::unique_ptr<IGUIProxyObject> JSI_GUIProxy<T>::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr)
151{
152 js::ProxyOptions options;
153 options.setClass(&JSInterface_GUIProxy::ClassDefinition());
154
155 auto ret = std::make_unique<IGUIProxyObject>();
156 ret->m_Ptr = static_cast<IGUIObject*>(ptr);
157
158 JS::RootedValue cppObj(rq.cx), data(rq.cx);
159 cppObj.get().setPrivate(ret->m_Ptr);
160 data.get().setPrivate(static_cast<void*>(dataPtr));
161 ret->m_Object.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
162 js::SetProxyReservedSlot(ret->m_Object, 0, data);
163 return ret;
164}
165
166template <typename T>
167bool JSI_GUIProxy<T>::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const
168{
169 ScriptRequest rq(cx);
170
171 T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
172 if (!e)
173 return false;
174
175 JS::RootedValue idval(rq.cx);
176 if (!JS_IdToValue(rq.cx, id, &idval))
177 return false;
178
179 std::string propName;
180 if (!Script::FromJSVal(rq, idval, propName))
181 return false;
182
183 // Return function properties. Specializable.
184 if (PropGetter(proxy, propName, vp))
185 return true;
186
187 // Use onWhatever to access event handlers
188 if (propName.substr(0, 2) == "on")
189 {
190 CStr eventName(propName.substr(2));
191 std::map<CStr, JS::Heap<JSObject*>>::iterator it = e->m_ScriptHandlers.find(eventName);
192 if (it == e->m_ScriptHandlers.end())
193 vp.setNull();
194 else
195 vp.setObject(*it->second.get());
196 return true;
197 }
198
199 if (propName == "parent")
200 {
201 IGUIObject* parent = e->GetParent();
202
203 if (parent)
204 vp.set(JS::ObjectValue(*parent->GetJSObject()));
205 else
206 vp.set(JS::NullValue());
207
208 return true;
209 }
210 else if (propName == "children")
211 {
212 Script::CreateArray(rq, vp);
213
214 for (size_t i = 0; i < e->m_Children.size(); ++i)
215 Script::SetPropertyInt(rq, vp, i, e->m_Children[i]);
216
217 return true;
218 }
219 else if (propName == "name")
220 {
221 Script::ToJSVal(rq, vp, e->GetName());
222 return true;
223 }
224 else if (e->SettingExists(propName))
225 {
226 e->m_Settings[propName]->ToJSVal(rq, vp);
227 return true;
228 }
229
230 LOGERROR("Property '%s' does not exist!", propName.c_str());
231 return false;
232}
233
234
235template <typename T>
236bool JSI_GUIProxy<T>::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
237 JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const
238{
239 T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
240 if (!e)
241 {
242 LOGERROR("C++ GUI Object could not be found");
243 return result.fail(JSMSG_OBJECT_REQUIRED);
244 }
245
246 ScriptRequest rq(cx);
247
248 JS::RootedValue idval(rq.cx);
249 if (!JS_IdToValue(rq.cx, id, &idval))
250 return result.fail(JSMSG_BAD_PROP_ID);
251
252 std::string propName;
253 if (!Script::FromJSVal(rq, idval, propName))
254 return result.fail(JSMSG_BAD_PROP_ID);
255
256 if (propName == "name")
257 {
258 std::string value;
259 if (!Script::FromJSVal(rq, vp, value))
260 return result.fail(JSMSG_BAD_PROP_ID);
261 e->SetName(value);
262 return result.succeed();
263 }
264
265 JS::RootedObject vpObj(cx);
266 if (vp.isObject())
267 vpObj = &vp.toObject();
268
269 // Use onWhatever to set event handlers
270 if (propName.substr(0, 2) == "on")
271 {
272 if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(&vp.toObject()))
273 {
274 LOGERROR("on- event-handlers must be functions");
275 return result.fail(JSMSG_NOT_FUNCTION);
276 }
277
278 CStr eventName(propName.substr(2));
279 e->SetScriptHandler(eventName, vpObj);
280
281 return result.succeed();
282 }
283
284 if (e->SettingExists(propName))
285 return e->m_Settings[propName]->FromJSVal(rq, vp, true) ? result.succeed() : result.fail(JSMSG_USER_DEFINED_ERROR);
286
287 LOGERROR("Property '%s' does not exist!", propName.c_str());
288 return result.fail(JSMSG_BAD_PROP_ID);
289}
290
291template<typename T>
292bool JSI_GUIProxy<T>::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const
293{
294 T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
295 if (!e)
296 {
297 LOGERROR("C++ GUI Object could not be found");
298 return result.fail(JSMSG_OBJECT_REQUIRED);
299 }
300
301 ScriptRequest rq(cx);
302
303 JS::RootedValue idval(rq.cx);
304 if (!JS_IdToValue(rq.cx, id, &idval))
305 return result.fail(JSMSG_BAD_PROP_ID);
306
307 std::string propName;
308 if (!Script::FromJSVal(rq, idval, propName))
309 return result.fail(JSMSG_BAD_PROP_ID);
310
311 // event handlers
312 if (std::string_view{propName}.substr(0, 2) == "on")
313 {
314 CStr eventName(propName.substr(2));
315 e->UnsetScriptHandler(eventName);
316 return result.succeed();
317 }
318
319 LOGERROR("Only event handlers can be deleted from GUI objects!");
320 return result.fail(JSMSG_BAD_PROP_ID);
321}
#define LOGERROR(...)
Definition: CLogger.h:37
Proxies need to store some data whose lifetime is tied to an interface.
Definition: JSInterface_GUIProxy.h:86
virtual bool setFunction(const ScriptRequest &rq, const std::string &name, JSFunction *function)=0
GUI object such as a button or an input-box.
Definition: IGUIObject.h:60
IGUIObject * GetParent() const
NOTE! This will not just return m_pParent, when that is need use it! There is one exception to it,...
Definition: IGUIObject.cpp:187
JSObject * GetJSObject()
Retrieves the JSObject representing this GUI object.
Definition: IGUIObject.cpp:427
Handles the js interface with C++ GUI objects.
Definition: JSInterface_GUIProxy.h:121
virtual bool get(JSContext *cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const override final
Definition: JSInterface_GUIProxy_impl.h:167
static std::unique_ptr< IGUIProxyObject > CreateJSObject(const ScriptRequest &rq, GUIObjectType *ptr, GUIProxyProps *data)
Definition: JSInterface_GUIProxy_impl.h:150
bool PropGetter(JS::HandleObject proxy, const std::string &propName, JS::MutableHandleValue vp) const
Definition: JSInterface_GUIProxy_impl.h:113
static JSI_GUIProxy & Singleton()
Definition: JSInterface_GUIProxy_impl.h:35
static GUIObjectType * FromPrivateSlot(const ScriptRequest &, JS::CallArgs &args)
Definition: JSInterface_GUIProxy_impl.h:106
virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, JS::HandleValue receiver, JS::ObjectOpResult &result) const final
Definition: JSInterface_GUIProxy_impl.h:236
static void CreateFunctions(const ScriptRequest &rq, GUIProxyProps *cache)
virtual bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override final
Definition: JSInterface_GUIProxy_impl.h:292
static std::pair< const js::BaseProxyHandler *, GUIProxyProps * > CreateData(ScriptInterface &scriptInterface)
Definition: JSInterface_GUIProxy_impl.h:127
static void CreateFunction(const ScriptRequest &rq, GUIProxyProps *cache, const std::string &name)
Definition: JSInterface_GUIProxy_impl.h:144
Definition: JSInterface_GUIProxy_impl.h:63
virtual JSObject * get(const std::string &name) const override
Definition: JSInterface_GUIProxy_impl.h:72
virtual ~MapCache()
Definition: JSInterface_GUIProxy_impl.h:65
virtual bool setFunction(const ScriptRequest &rq, const std::string &name, JSFunction *function) override
Definition: JSInterface_GUIProxy_impl.h:77
std::unordered_map< std::string, JS::PersistentRootedObject > m_Functions
Definition: JSInterface_GUIProxy_impl.h:84
virtual bool has(const std::string &name) const override
Definition: JSInterface_GUIProxy_impl.h:67
Abstraction around a SpiderMonkey JS::Realm.
Definition: ScriptInterface.h:72
Spidermonkey maintains some 'local' state via the JSContext* object.
Definition: ScriptRequest.h:60
JSContext * cx
Definition: ScriptRequest.h:92
Template base class for singletons.
Definition: Singleton.h:34
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning.
Definition: code_annotation.h:40
Definition: JSInterface_GUIProxy_impl.h:53
JSClass & ClassDefinition()
Definition: JSInterface_GUIProxy_impl.h:55
bool FromJSVal(const ScriptRequest &rq, const JS::HandleValue val, T &ret)
Convert a JS::Value to a C++ type.
void ToJSVal(const ScriptRequest &rq, JS::MutableHandleValue ret, T const &val)
Convert a C++ type to a JS::Value.
bool CreateArray(const ScriptRequest &rq, JS::MutableHandleValue objectValue, size_t length=0)
Sets the given value to a new JS object or Null Value in case of out-of-memory.
Definition: Object.h:244
bool SetPropertyInt(const ScriptRequest &rq, JS::HandleValue obj, int name, const T &value, bool constant=false, bool enumerable=true)
Definition: Object.h:129
#define T(string_literal)
Definition: secure_crt.cpp:77
Definition: JSInterface_GUIProxy_impl.h:101