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