Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
ScriptInterface.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#ifndef INCLUDED_SCRIPTINTERFACE
19#define INCLUDED_SCRIPTINTERFACE
20
21#include "ps/Errors.h"
26
27#include <map>
28
29ERROR_GROUP(Scripting);
30ERROR_TYPE(Scripting, SetupFailed);
31
32ERROR_SUBGROUP(Scripting, LoadFile);
33ERROR_TYPE(Scripting_LoadFile, OpenFailed);
34ERROR_TYPE(Scripting_LoadFile, EvalErrors);
35
36ERROR_TYPE(Scripting, CallFunctionFailed);
37ERROR_TYPE(Scripting, DefineConstantFailed);
38ERROR_TYPE(Scripting, CreateObjectFailed);
39ERROR_TYPE(Scripting, TypeDoesNotExist);
40
41ERROR_SUBGROUP(Scripting, DefineType);
42ERROR_TYPE(Scripting_DefineType, AlreadyExists);
43ERROR_TYPE(Scripting_DefineType, CreationFailed);
44
45// Set the maximum number of function arguments that can be handled
46// (This should be as small as possible (for compiler efficiency),
47// but as large as necessary for all wrapped functions)
48#define SCRIPT_INTERFACE_MAX_ARGS 8
49
50class ScriptInterface;
52
53class ScriptContext;
54// Using a global object for the context is a workaround until Simulation, AI, etc,
55// use their own threads and also their own contexts.
56extern thread_local std::shared_ptr<ScriptContext> g_ScriptContext;
57
58namespace boost { namespace random { class rand48; } }
59
60class Path;
61using VfsPath = Path;
62
63/**
64 * Abstraction around a SpiderMonkey JS::Realm.
65 *
66 * Thread-safety:
67 * - May be used in non-main threads.
68 * - Each ScriptInterface must be created, used, and destroyed, all in a single thread
69 * (it must never be shared between threads).
70 */
72{
74
75 friend class ScriptRequest;
76
77public:
78
79 /**
80 * Constructor.
81 * @param nativeScopeName Name of global object that functions (via ScriptFunction::Register) will
82 * be placed into, as a scoping mechanism; typically "Engine"
83 * @param debugName Name of this interface for CScriptStats purposes.
84 * @param context ScriptContext to use when initializing this interface.
85 */
86 ScriptInterface(const char* nativeScopeName, const char* debugName, ScriptContext& context);
87
88 template<typename Context>
89 ScriptInterface(const char* nativeScopeName, const char* debugName, Context&& context) :
90 ScriptInterface(nativeScopeName, debugName, *context)
91 {
92 static_assert(std::is_lvalue_reference_v<Context>, "`ScriptInterface` doesn't take ownership "
93 "of the context.");
94 }
95
96 /**
97 * Alternate constructor. This creates the new Realm in the same Compartment as the neighbor scriptInterface.
98 * This means that data can be freely exchanged between these two script interfaces without cloning.
99 * @param nativeScopeName Name of global object that functions (via ScriptFunction::Register) will
100 * be placed into, as a scoping mechanism; typically "Engine"
101 * @param debugName Name of this interface for CScriptStats purposes.
102 * @param scriptInterface 'Neighbor' scriptInterface to share a compartment with.
103 */
104 ScriptInterface(const char* nativeScopeName, const char* debugName, const ScriptInterface& neighbor);
105
107
109 {
110 friend class ScriptInterface;
111 public:
112 static const ScriptInterface& GetScriptInterface(JSContext* cx);
113 static void* GetCBData(JSContext* cx);
114 protected:
115 ScriptInterface* pScriptInterface; // the ScriptInterface object the compartment belongs to
116 void* pCBData; // meant to be used as the "this" object for callback functions
117 };
118
119 void SetCallbackData(void* pCBData);
120
121 /**
122 * Convert the CmptPrivate callback data to T*
123 */
124 template <typename T>
126 {
127 static_assert(!std::is_same_v<void, T>);
128 return static_cast<T*>(ObjectFromCBData<void>(rq));
129 }
130
131 /**
132 * Variant for the function wrapper.
133 */
134 template <typename T>
135 static T* ObjectFromCBData(const ScriptRequest& rq, JS::CallArgs&)
136 {
137 return ObjectFromCBData<T>(rq);
138 }
139
140 /**
141 * GetGeneralJSContext returns the context without starting a GC request and without
142 * entering the ScriptInterface compartment. It should only be used in specific situations,
143 * for instance when initializing a persistent rooted.
144 * If you need the compartmented context of the ScriptInterface, you should create a
145 * ScriptInterface::Request and use the context from that.
146 */
147 JSContext* GetGeneralJSContext() const;
148 ScriptContext& GetContext() const;
149
150 /**
151 * Load global scripts that most script interfaces need,
152 * located in the /globalscripts directory. VFS must be initialized.
153 */
154 bool LoadGlobalScripts();
155
156 /**
157 * Replace the default JS random number generator with a seeded, network-synced one.
158 */
159 bool ReplaceNondeterministicRNG(boost::random::rand48& rng);
160
161 /**
162 * Call a constructor function, equivalent to JS "new ctor(arg)".
163 * @param ctor An object that can be used as constructor
164 * @param argv Constructor arguments
165 * @param out The new object; On error an error message gets logged and out is Null (out.isNull() == true).
166 */
167 void CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const;
168
169 JSObject* CreateCustomObject(const std::string & typeName) const;
170 void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs);
171
172 /**
173 * Set the named property on the global object.
174 * Optionally makes it {ReadOnly, DontEnum}. We do not allow to make it DontDelete, so that it can be hotloaded
175 * by deleting it and re-creating it, which is done by setting @p replace to true.
176 */
177 template<typename T>
178 bool SetGlobal(const char* name, const T& value, bool replace = false, bool constant = true, bool enumerate = true);
179
180 /**
181 * Get an object from the global scope or any lexical scope.
182 * This can return globally accessible objects even if they are not properties
183 * of the global object (e.g. ES6 class definitions).
184 * @param name - Name of the property.
185 * @param out The object or null.
186 */
187 static bool GetGlobalProperty(const ScriptRequest& rq, const std::string& name, JS::MutableHandleValue out);
188
189 bool SetPrototype(JS::HandleValue obj, JS::HandleValue proto);
190
191 /**
192 * Load and execute the given script in a new function scope.
193 * @param filename Name for debugging purposes (not used to load the file)
194 * @param code JS code to execute
195 * @return true on successful compilation and execution; false otherwise
196 */
197 bool LoadScript(const VfsPath& filename, const std::string& code) const;
198
199 /**
200 * Load and execute the given script in the global scope.
201 * @param filename Name for debugging purposes (not used to load the file)
202 * @param code JS code to execute
203 * @return true on successful compilation and execution; false otherwise
204 */
205 bool LoadGlobalScript(const VfsPath& filename, const std::string& code) const;
206
207 /**
208 * Load and execute the given script in the global scope.
209 * @return true on successful compilation and execution; false otherwise
210 */
211 bool LoadGlobalScriptFile(const VfsPath& path) const;
212
213 /**
214 * Evaluate some JS code in the global scope.
215 * @return true on successful compilation and execution; false otherwise
216 */
217 bool Eval(const char* code) const;
218 bool Eval(const char* code, JS::MutableHandleValue out) const;
219 template<typename T> bool Eval(const char* code, T& out) const;
220
221 /**
222 * Calls the random number generator assigned to this ScriptInterface instance and returns the generated number.
223 */
224 bool MathRandom(double& nbr) const;
225
226 /**
227 * JSNative wrapper of the above.
228 */
229 static bool Math_random(JSContext* cx, uint argc, JS::Value* vp);
230
231 /**
232 * Retrieve the private data field of a JSObject that is an instance of the given JSClass.
233 */
234 template <typename T>
235 static T* GetPrivate(const ScriptRequest& rq, JS::HandleObject thisobj, JSClass* jsClass)
236 {
237 T* value = static_cast<T*>(JS_GetInstancePrivate(rq.cx, thisobj, jsClass, nullptr));
238
239 if (value == nullptr)
240 ScriptException::Raise(rq, "Private data of the given object is null!");
241
242 return value;
243 }
244
245 /**
246 * Retrieve the private data field of a JS Object that is an instance of the given JSClass.
247 * If an error occurs, GetPrivate will report it with the according stack.
248 */
249 template <typename T>
250 static T* GetPrivate(const ScriptRequest& rq, JS::CallArgs& callArgs, JSClass* jsClass)
251 {
252 if (!callArgs.thisv().isObject())
253 {
254 ScriptException::Raise(rq, "Cannot retrieve private JS class data because from a non-object value!");
255 return nullptr;
256 }
257
258 JS::RootedObject thisObj(rq.cx, &callArgs.thisv().toObject());
259 T* value = static_cast<T*>(JS_GetInstancePrivate(rq.cx, thisObj, jsClass, &callArgs));
260
261 if (value == nullptr)
262 ScriptException::Raise(rq, "Private data of the given object is null!");
263
264 return value;
265 }
266
267private:
268 bool SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate);
269
271 {
272 JS::PersistentRootedObject m_Prototype;
273 JSClass* m_Class;
275 };
276
278
279 // Take care to keep this declaration before heap rooted members. Destructors of heap rooted
280 // members have to be called before the custom destructor of ScriptInterface_impl.
281 std::unique_ptr<ScriptInterface_impl> m;
282
283 std::map<std::string, CustomType> m_CustomObjectTypes;
284};
285
286// Explicitly instantiate void* as that is used for the generic template,
287// and we want to define it in the .cpp file.
288template <> void* ScriptInterface::ObjectFromCBData(const ScriptRequest& rq);
289
290template<typename T>
291bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace, bool constant, bool enumerate)
292{
293 ScriptRequest rq(this);
294 JS::RootedValue val(rq.cx);
295 Script::ToJSVal(rq, &val, value);
296 return SetGlobal_(name, val, replace, constant, enumerate);
297}
298
299template<typename T>
300bool ScriptInterface::Eval(const char* code, T& ret) const
301{
302 ScriptRequest rq(this);
303 JS::RootedValue rval(rq.cx);
304 if (!Eval(code, &rval))
305 return false;
306 return Script::FromJSVal(rq, rval, ret);
307}
308
309#endif // INCLUDED_SCRIPTINTERFACE
ERROR_GROUP(Scripting)
thread_local std::shared_ptr< ScriptContext > g_ScriptContext
Definition: GameSetup.cpp:108
ERROR_SUBGROUP(Scripting, LoadFile)
ERROR_TYPE(Scripting, SetupFailed)
Definition: path.h:80
Path()
Definition: path.h:84
Abstraction around a SpiderMonkey JSContext.
Definition: ScriptContext.h:46
Abstraction around a SpiderMonkey JS::Realm.
Definition: ScriptInterface.h:72
void SetCallbackData(void *pCBData)
Definition: ScriptInterface.cpp:408
NONCOPYABLE(ScriptInterface)
bool SetGlobal(const char *name, const T &value, bool replace=false, bool constant=true, bool enumerate=true)
Set the named property on the global object.
Definition: ScriptInterface.h:291
bool MathRandom(double &nbr) const
Calls the random number generator assigned to this ScriptInterface instance and returns the generated...
Definition: ScriptInterface.cpp:284
std::map< std::string, CustomType > m_CustomObjectTypes
Definition: ScriptInterface.h:283
bool ReplaceNondeterministicRNG(boost::random::rand48 &rng)
Replace the default JS random number generator with a seeded, network-synced one.
Definition: ScriptInterface.cpp:438
JSContext * GetGeneralJSContext() const
GetGeneralJSContext returns the context without starting a GC request and without entering the Script...
Definition: ScriptInterface.cpp:460
static T * ObjectFromCBData(const ScriptRequest &rq)
Convert the CmptPrivate callback data to T*.
Definition: ScriptInterface.h:125
JSObject * CreateCustomObject(const std::string &typeName) const
Definition: ScriptInterface.cpp:518
bool Eval(const char *code) const
Evaluate some JS code in the global scope.
Definition: ScriptInterface.cpp:691
bool SetGlobal_(const char *name, JS::HandleValue value, bool replace, bool constant, bool enumerate)
Definition: ScriptInterface.cpp:530
ScriptInterface(const char *nativeScopeName, const char *debugName, Context &&context)
Definition: ScriptInterface.h:89
static bool Math_random(JSContext *cx, uint argc, JS::Value *vp)
JSNative wrapper of the above.
Definition: ScriptInterface.cpp:292
bool SetPrototype(JS::HandleValue obj, JS::HandleValue proto)
Definition: ScriptInterface.cpp:604
CmptPrivate m_CmptPrivate
Definition: ScriptInterface.h:277
std::unique_ptr< ScriptInterface_impl > m
Definition: ScriptInterface.h:281
void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs)
Definition: ScriptInterface.cpp:487
static T * ObjectFromCBData(const ScriptRequest &rq, JS::CallArgs &)
Variant for the function wrapper.
Definition: ScriptInterface.h:135
bool LoadGlobalScripts()
Load global scripts that most script interfaces need, located in the /globalscripts directory.
Definition: ScriptInterface.cpp:419
ScriptInterface(const char *nativeScopeName, const char *debugName, ScriptContext &context)
Constructor.
Definition: ScriptInterface.cpp:352
bool LoadGlobalScriptFile(const VfsPath &path) const
Load and execute the given script in the global scope.
Definition: ScriptInterface.cpp:668
static bool GetGlobalProperty(const ScriptRequest &rq, const std::string &name, JS::MutableHandleValue out)
Get an object from the global scope or any lexical scope.
Definition: ScriptInterface.cpp:574
~ScriptInterface()
Definition: ScriptInterface.cpp:385
bool LoadScript(const VfsPath &filename, const std::string &code) const
Load and execute the given script in a new function scope.
Definition: ScriptInterface.cpp:614
static T * GetPrivate(const ScriptRequest &rq, JS::CallArgs &callArgs, JSClass *jsClass)
Retrieve the private data field of a JS Object that is an instance of the given JSClass.
Definition: ScriptInterface.h:250
ScriptContext & GetContext() const
Definition: ScriptInterface.cpp:465
bool LoadGlobalScript(const VfsPath &filename, const std::string &code) const
Load and execute the given script in the global scope.
Definition: ScriptInterface.cpp:648
void CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const
Call a constructor function, equivalent to JS "new ctor(arg)".
Definition: ScriptInterface.cpp:470
static T * GetPrivate(const ScriptRequest &rq, JS::HandleObject thisobj, JSClass *jsClass)
Retrieve the private data field of a JSObject that is an instance of the given JSClass.
Definition: ScriptInterface.h:235
Spidermonkey maintains some 'local' state via the JSContext* object.
Definition: ScriptRequest.h:60
JSContext * cx
Definition: ScriptRequest.h:92
void Raise(const ScriptRequest &rq, const char *format,...)
Raise a JS exception from C++ code.
Definition: ScriptExceptions.cpp:95
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.
Definition: pch_boost.h:51
Definition: pch_boost.h:50
#define T(string_literal)
Definition: secure_crt.cpp:77
Definition: ScriptInterface.h:109
ScriptInterface * pScriptInterface
Definition: ScriptInterface.h:115
void * pCBData
Definition: ScriptInterface.h:116
static void * GetCBData(JSContext *cx)
Definition: ScriptInterface.cpp:402
static const ScriptInterface & GetScriptInterface(JSContext *cx)
Definition: ScriptInterface.cpp:395
Definition: ScriptInterface.h:271
JSNative m_Constructor
Definition: ScriptInterface.h:274
JS::PersistentRootedObject m_Prototype
Definition: ScriptInterface.h:272
JSClass * m_Class
Definition: ScriptInterface.h:273
Definition: ScriptInterface.cpp:54
unsigned int uint
Definition: types.h:42
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:407