Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ScriptableObject.h
Go to the documentation of this file.
1 /* Copyright (C) 2009 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 // ScriptableObject.h
19 //
20 // A quick way to add (mostly) sensibly-behaving JavaScript interfaces to native classes.
21 //
22 
23 #ifndef INCLUDED_SCRIPTABLEOBJECT
24 #define INCLUDED_SCRIPTABLEOBJECT
25 
26 #include <boost/unordered_map.hpp>
28 #include "JSConversions.h"
29 
30 #include "lib/sysdep/stl.h"
31 
32 #define ALLOW_NONSHARED_NATIVES
33 
34 class IJSObject;
35 
37 {
38 public:
39  virtual ~IJSProperty() {};
40  virtual jsval Get( JSContext* cx, IJSObject* obj ) = 0;
41  virtual void Set( JSContext* cx, IJSObject* obj, jsval value ) = 0;
42 };
43 
44 class IJSObject
45 {
46 public:
47  typedef boost::unordered_map<CStrW, IJSProperty*> PropertyTable;
48 
49  // Property getters and setters
50  typedef jsval (IJSObject::*GetFn)( JSContext* cx );
51  typedef void (IJSObject::*SetFn)( JSContext* cx, jsval value );
52 
53  // Return a pointer to a property, if it exists
54  virtual IJSProperty* HasProperty( const CStrW& PropertyName ) = 0;
55 
56  // Retrieve the value of a property (returning false if that property is not defined)
57  virtual bool GetProperty( JSContext* cx, const CStrW& PropertyName, jsval* vp ) = 0;
58 
59  // Add a property (with immediate value)
60  virtual void AddProperty( const CStrW& PropertyName, jsval Value ) = 0;
61 
62  inline IJSObject() {}
63  virtual ~IJSObject() {}
64 };
65 
66 template<typename T, bool ReadOnly = false> class CJSObject;
67 
68 template<typename T, bool ReadOnly> class CJSProperty : public IJSProperty
69 {
71 
72 public:
74  {
75  m_Data = Data;
76  }
77  jsval Get( JSContext* UNUSED(cx), IJSObject* owner )
78  {
79  return( ToJSVal( owner->*m_Data ) );
80  }
81  void Set( JSContext* cx, IJSObject* owner, jsval Value )
82  {
83  if( !ReadOnly )
84  ToPrimitive( cx, Value, owner->*m_Data );
85  }
86 };
87 
88 #ifdef ALLOW_NONSHARED_NATIVES
89 
90 template<typename T, bool ReadOnly> class CJSNonsharedProperty : public IJSProperty
91 {
93 
94 public:
96  {
97  m_Data = Data;
98  }
99  jsval Get( JSContext* UNUSED(cx), IJSObject* UNUSED(owner) )
100  {
101  return( ToJSVal( *m_Data ) );
102  }
103  void Set( JSContext* cx, IJSObject* UNUSED(owner), jsval Value )
104  {
105  if( !ReadOnly )
106  ToPrimitive( cx, Value, *m_Data );
107  }
108 };
109 
110 #endif /* ALLOW_NONSHARED_NATIVES */
111 
113 {
114  // Function on Owner to get the value
116 
117  // Function on Owner to set the value
119 
120 public:
122  {
123  m_Getter = Getter;
124  m_Setter = Setter;
125  // Must at least be able to read
126  ENSURE( m_Getter );
127  }
128  jsval Get( JSContext* cx, IJSObject* obj )
129  {
130  return( (obj->*m_Getter)(cx) );
131  }
132  void Set( JSContext* cx, IJSObject* obj, jsval value )
133  {
134  if( m_Setter )
135  (obj->*m_Setter)( cx, value );
136  }
137 };
138 
140 {
141  template<typename Q, bool ReadOnly> friend class CJSObject;
142 
143  jsval m_Data;
144 
145 public:
146  CJSValProperty( jsval Data )
147  {
148  m_Data = Data;
149  Root();
150  }
152  {
153  Uproot();
154  }
155  void Root()
156  {
157  if( JSVAL_IS_GCTHING( m_Data ) )
158  JS_AddNamedValueRoot( g_ScriptingHost.GetContext(), &m_Data, "ScriptableObjectProperty" );
159  }
160  void Uproot()
161  {
162  if( JSVAL_IS_GCTHING( m_Data ))
163  JS_RemoveValueRoot( g_ScriptingHost.GetContext(), &m_Data );
164  }
165  jsval Get( JSContext* UNUSED(cx), IJSObject* UNUSED(object))
166  {
167  return( m_Data );
168  }
169  void Set( JSContext* UNUSED(cx), IJSObject* UNUSED(owner), jsval value )
170  {
171  Uproot();
172  m_Data = value;
173  Root();
174  }
175 };
176 
177 // Wrapper around native functions that are attached to CJSObjects
178 
179 template<typename T, bool ReadOnly, typename RType, RType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )> class CNativeFunction
180 {
181 public:
182  static JSBool JSFunction( JSContext* cx, uintN argc, jsval* vp )
183  {
184  T* Native = ToNative<T>( cx, JS_THIS_OBJECT( cx, vp ) );
185  if( !Native )
186  return( JS_FALSE );
187 
188  jsval rval = ToJSVal<RType>( (Native->*NativeFunction)( cx, argc, JS_ARGV(cx, vp) ) );
189  JS_SET_RVAL( cx, vp, rval );
190  return( JS_TRUE );
191  }
192 };
193 
194 // Special case for void functions
195 template<typename T, bool ReadOnly, void (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
196 class CNativeFunction<T, ReadOnly, void, NativeFunction>
197 {
198 public:
199  static JSBool JSFunction( JSContext* cx, uintN argc, jsval* vp )
200  {
201  T* Native = ToNative<T>( cx, JS_THIS_OBJECT( cx, vp ) );
202  if( !Native )
203  return( JS_FALSE );
204 
205  (Native->*NativeFunction)( cx, argc, JS_ARGV(cx, vp) );
206 
207  JS_SET_RVAL( cx, vp, JSVAL_VOID );
208  return( JS_TRUE );
209  }
210 };
211 
212 template<typename T, bool ReadOnly> class CJSObject : public IJSObject
213 {
214  // This object
215  JSObject* m_JS;
216 
217 protected:
218  // The properties defined by the engine
220 #ifdef ALLOW_NONSHARED_NATIVES
222 #endif
223  // Properties added by script
225 
226  // Whether native code is responsible for managing this object.
227  // Script constructors should clear this *BEFORE* creating a JS
228  // mirror (otherwise it'll be rooted).
229 
231 
232 public:
233  // Property getters and setters
234  typedef jsval (T::*TGetFn)( JSContext* );
235  typedef void (T::*TSetFn)( JSContext*, jsval value );
236 
237  static JSClass JSI_class;
238 
239  static void ScriptingInit( const char* ClassName, JSNative Constructor = NULL, uintN ConstructorMinArgs = 0 )
240  {
241  JSFunctionSpec* JSI_methods = new JSFunctionSpec[ m_Methods.size() + 1 ];
242  size_t MethodID;
243  for( MethodID = 0; MethodID < m_Methods.size(); ++MethodID )
244  JSI_methods[MethodID] = m_Methods[MethodID];
245 
246  JSI_methods[MethodID].name = 0;
247 
248  JSI_class.name = ClassName;
249  g_ScriptingHost.DefineCustomObjectType( &JSI_class, Constructor, ConstructorMinArgs, JSI_props, JSI_methods, NULL, NULL );
250 
251  delete[]( JSI_methods );
252 
253  atexit( ScriptingShutdown );
254  }
255  static void ScriptingShutdown()
256  {
257  PropertyTable::iterator it;
258  for( it = m_NativeProperties.begin(); it != m_NativeProperties.end(); ++it )
259  delete( it->second );
260  m_NativeProperties.clear();
261  }
262 
263  // JS Property access
264  bool GetProperty( JSContext* cx, const CStrW& PropertyName, jsval* vp )
265  {
266  IJSProperty* Property = HasProperty( PropertyName );
267  if( Property )
268  *vp = Property->Get( cx, this );
269 
270  return( true );
271  }
272  void SetProperty( JSContext* cx, const CStrW& PropertyName, jsval* vp )
273  {
274  if( !ReadOnly )
275  {
276  IJSProperty* prop = HasProperty( PropertyName );
277 
278  if( prop )
279  {
280  // Already exists
281  prop->Set( cx, this, *vp );
282  }
283  else
284  {
285  // Need to add it
286  AddProperty( PropertyName, *vp );
287  }
288  }
289  }
290 
291  IJSProperty* HasProperty( const CStrW& PropertyName )
292  {
293  PropertyTable::iterator it;
294 
295  // Engine-defined properties take precedence
296  it = m_NativeProperties.find( PropertyName );
297  if( it != m_NativeProperties.end() )
298  return( it->second );
299  // Next are the object-local engine-defined properties
300  // (if they're compiled in)
301 #ifdef ALLOW_NONSHARED_NATIVES
302  it = m_NonsharedProperties.find( PropertyName );
303  if( it != m_NonsharedProperties.end() )
304  return( it->second );
305 #endif
306  // Then check in script properties
307  it = m_ScriptProperties.find( PropertyName );
308  if( it != m_ScriptProperties.end() )
309  return( it->second );
310 
311  // Otherwise not found
312  return( NULL );
313  }
314 
315  void AddProperty( const CStrW& PropertyName, jsval Value )
316  {
317  ENSURE( !HasProperty( PropertyName ) );
318  CJSValProperty* newProp = new CJSValProperty( Value );
319  m_ScriptProperties[PropertyName] = newProp;
320  }
321  static void AddProperty( const CStrW& PropertyName, TGetFn Getter, TSetFn Setter = NULL )
322  {
323  m_NativeProperties[PropertyName] = new CJSFunctionProperty( (GetFn)Getter, (SetFn)Setter );
324  }
325  template<typename ReturnType, ReturnType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
326  static void AddMethod( const char* Name, uintN MinArgs )
327  {
328  JSFunctionSpec FnInfo = { Name, CNativeFunction<T, ReadOnly, ReturnType, NativeFunction>::JSFunction, (uint8)MinArgs, 0 };
329  m_Methods.push_back( FnInfo );
330  }
331  template<typename PropType> static void AddProperty( const CStrW& PropertyName, PropType T::*Native, bool PropReadOnly = ReadOnly )
332  {
333  IJSProperty* prop;
334  if( PropReadOnly )
335  {
336  prop = new CJSProperty<PropType, true>( (PropType IJSObject::*)Native );
337  }
338  else
339  {
340  prop = new CJSProperty<PropType, ReadOnly>( (PropType IJSObject::*)Native );
341  }
342  m_NativeProperties[PropertyName] = prop;
343  }
344 #ifdef ALLOW_NONSHARED_NATIVES
345  template<typename PropType> void AddLocalProperty( const CStrW& PropertyName, PropType* Native, bool PropReadOnly = ReadOnly )
346  {
347  IJSProperty* prop;
348  if( PropReadOnly )
349  {
350  prop = new CJSNonsharedProperty<PropType, true>( Native );
351  }
352  else
353  prop = new CJSNonsharedProperty<PropType, ReadOnly>( Native );
354  m_NonsharedProperties[PropertyName] = prop;
355  }
356 #endif
357 
358  // Object operations
359  JSObject* GetScript()
360  {
361  if( !m_JS )
363  return( m_JS );
364  }
365  // Creating and releasing script objects is done automatically most of the time, but you
366  // can do it explicitly.
368  {
369  if( !m_JS )
370  {
371  m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
372  if( m_EngineOwned )
373  JS_AddNamedObjectRoot( g_ScriptingHost.GetContext(), &m_JS, JSI_class.name );
374 
375  JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, (T*)this );
376  }
377  }
379  {
380  if( m_JS )
381  {
382  JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL );
383  if( m_EngineOwned )
384  JS_RemoveObjectRoot( g_ScriptingHost.GetContext(), &m_JS );
385  m_JS = NULL;
386  }
387  }
388 
389  //
390  // Functions and data that must be provided to JavaScript
391  //
392 private:
393  static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsid id, jsval* vp )
394  {
395  T* Instance = ToNative<T>( cx, obj );
396  if( !Instance )
397  return( JS_TRUE );
398 
399  jsval idval;
400  if( !JS_IdToValue( cx, id, &idval ) )
401  return( JS_FALSE );
402 
403  CStrW PropName = g_ScriptingHost.ValueToUCString( idval );
404 
405  if( !Instance->GetProperty( cx, PropName, vp ) )
406  return( JS_TRUE );
407 
408  return( JS_TRUE );
409  }
410  static JSBool JSSetProperty( JSContext* cx, JSObject* obj, jsid id, JSBool UNUSED(strict), jsval* vp )
411  {
412  T* Instance = ToNative<T>( cx, obj );
413  if( !Instance )
414  return( JS_TRUE );
415 
416  jsval idval;
417  if( !JS_IdToValue( cx, id, &idval ) )
418  return( JS_FALSE );
419 
420  CStrW PropName = g_ScriptingHost.ValueToUCString( idval );
421 
422  Instance->SetProperty( cx, PropName, vp );
423 
424  return( JS_TRUE );
425  }
426  static void DefaultFinalize( JSContext *cx, JSObject *obj )
427  {
428  T* Instance = ToNative<T>( cx, obj );
429  if( !Instance || Instance->m_EngineOwned )
430  return;
431 
432  delete( Instance );
433  JS_SetPrivate( cx, obj, NULL );
434  }
435 
436 
437  static JSPropertySpec JSI_props[];
438  static std::vector<JSFunctionSpec> m_Methods;
439 
440 public:
441  // Standard constructors/destructors
443  {
444  m_JS = NULL;
445  m_EngineOwned = true;
446  }
447  virtual ~CJSObject()
448  {
449  Shutdown();
450  }
451  void Shutdown()
452  {
453  PropertyTable::iterator it;
454  for( it = m_ScriptProperties.begin(); it != m_ScriptProperties.end(); ++it )
455  delete( it->second );
456  m_ScriptProperties.clear();
458 #ifdef ALLOW_NONSHARED_NATIVES
459  for( it = m_NonsharedProperties.begin(); it != m_NonsharedProperties.end(); ++it )
460  delete( it->second );
461  m_NonsharedProperties.clear();
462 #endif
463  }
464 };
465 
466 template<typename T, bool ReadOnly> JSClass CJSObject<T, ReadOnly>::JSI_class = {
467  NULL, JSCLASS_HAS_PRIVATE,
468  JS_PropertyStub, JS_PropertyStub,
470  JS_EnumerateStub, JS_ResolveStub,
471  JS_ConvertStub, DefaultFinalize,
472  NULL, NULL, NULL, NULL,
473  NULL, NULL, NULL, NULL
474 };
475 
476 template<typename T, bool ReadOnly> JSPropertySpec CJSObject<T, ReadOnly>::JSI_props[] = {
477  { NULL, 0, 0, NULL, NULL },
478 };
479 
480 template<typename T, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, ReadOnly>::m_Methods;
481 
482 template<typename T, bool ReadOnly> typename CJSObject<T, ReadOnly>::PropertyTable CJSObject<T, ReadOnly>::m_NativeProperties;
483 
484 #endif
485 
486 
487 
JSObject * GetScript()
CJSFunctionProperty(IJSObject::GetFn Getter, IJSObject::SetFn Setter)
void SetProperty(JSContext *cx, const CStrW &PropertyName, jsval *vp)
virtual ~IJSObject()
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
CJSProperty(T IJSObject::*Data)
PropertyTable m_NonsharedProperties
static JSBool JSFunction(JSContext *cx, uintN argc, jsval *vp)
IJSProperty * HasProperty(const CStrW &PropertyName)
CJSValProperty(jsval Data)
virtual void Set(JSContext *cx, IJSObject *obj, jsval value)=0
void CreateScriptObject()
static JSBool JSSetProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
static void ScriptingInit(const char *ClassName, JSNative Constructor=NULL, uintN ConstructorMinArgs=0)
jsval Get(JSContext *cx, IJSObject *obj)
void ReleaseScriptObject()
virtual jsval Get(JSContext *cx, IJSObject *obj)=0
void Set(JSContext *cx, IJSObject *owner, jsval Value)
static void ScriptingShutdown()
static void AddProperty(const CStrW &PropertyName, PropType T::*Native, bool PropReadOnly=ReadOnly)
IJSObject::GetFn m_Getter
static std::vector< JSFunctionSpec > m_Methods
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
virtual ~IJSProperty()
#define g_ScriptingHost
jsval Get(JSContext *cx, IJSObject *object)
static JSBool JSFunction(JSContext *cx, uintN argc, jsval *vp)
void(T::* TSetFn)(JSContext *, jsval value)
static void AddProperty(const CStrW &PropertyName, TGetFn Getter, TSetFn Setter=NULL)
void AddLocalProperty(const CStrW &PropertyName, PropType *Native, bool PropReadOnly=ReadOnly)
static void DefaultFinalize(JSContext *cx, JSObject *obj)
JSFunctionSpec JSI_methods[]
boost::unordered_map< CStrW, IJSProperty * > PropertyTable
bool GetProperty(JSContext *cx, const CStrW &PropertyName, jsval *vp)
void(IJSObject::* SetFn)(JSContext *cx, jsval value)
bool ToPrimitive(JSContext *cx, jsval v, T &Storage)
Definition: JSConversions.h:55
void Set(JSContext *cx, IJSObject *owner, jsval Value)
jsval Get(JSContext *cx, IJSObject *owner)
#define T(string_literal)
Definition: secure_crt.cpp:70
virtual bool GetProperty(JSContext *cx, const CStrW &PropertyName, jsval *vp)=0
void AddProperty(const CStrW &PropertyName, jsval Value)
jsval(IJSObject::* GetFn)(JSContext *cx)
void Set(JSContext *cx, IJSObject *owner, jsval value)
static PropertyTable m_NativeProperties
static void AddMethod(const char *Name, uintN MinArgs)
jsval Get(JSContext *cx, IJSObject *owner)
T IJSObject::* m_Data
void Set(JSContext *cx, IJSObject *obj, jsval value)
jsval ToJSVal(T &Native)
Definition: JSConversions.h:87
jsval(T::* TGetFn)(JSContext *)
static JSPropertySpec JSI_props[]
static JSBool JSGetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
static JSClass JSI_class
virtual ~CJSObject()
PropertyTable m_ScriptProperties
virtual IJSProperty * HasProperty(const CStrW &PropertyName)=0
virtual void AddProperty(const CStrW &PropertyName, jsval Value)=0
IJSObject::SetFn m_Setter
JSObject * m_JS