0ad/source/scripting/ScriptableObject.h
janwas c0ed950657 had to remove uint and ulong from lib/types.h due to conflict with other library.
this snowballed into a massive search+destroy of the hodgepodge of
mostly equivalent types we had in use (int, uint, unsigned, unsigned
int, i32, u32, ulong, uintN).

it is more efficient to use 64-bit types in 64-bit mode, so the
preferred default is size_t (for anything remotely resembling a size or
index). tile coordinates are ssize_t to allow more efficient conversion
to/from floating point. flags are int because we almost never need more
than 15 distinct bits, bit test/set is not slower and int is fastest to
type. finally, some data that is pretty much directly passed to OpenGL
is now typed accordingly.

after several hours, the code now requires fewer casts and less
guesswork.

other changes:
- unit and player IDs now have an "invalid id" constant in the
respective class to avoid casting and -1
- fix some endian/64-bit bugs in the map (un)packing. added a
convenience function to write/read a size_t.
- ia32: change CPUID interface to allow passing in ecx (required for
cache topology detection, which I need at work). remove some unneeded
functions from asm, replace with intrinsics where possible.

This was SVN commit r5942.
2008-05-11 18:48:32 +00:00

461 lines
12 KiB
C++

// ScriptableObject.h
//
// A quick way to add (mostly) sensibly-behaving JavaScript interfaces to native classes.
//
#include "scripting/ScriptingHost.h"
#include "JSConversions.h"
#ifndef INCLUDED_SCRIPTABLEOBJECT
#define INCLUDED_SCRIPTABLEOBJECT
#define ALLOW_NONSHARED_NATIVES
class IJSObject;
class IJSProperty
{
public:
virtual ~IJSProperty() {};
virtual jsval Get( JSContext* cx, IJSObject* obj ) = 0;
virtual void Set( JSContext* cx, IJSObject* obj, jsval value ) = 0;
};
class IJSObject
{
public:
typedef STL_HASH_MAP<CStrW, IJSProperty*, CStrW_hash_compare> PropertyTable;
// Property getters and setters
typedef jsval (IJSObject::*GetFn)( JSContext* cx );
typedef void (IJSObject::*SetFn)( JSContext* cx, jsval value );
// Return a pointer to a property, if it exists
virtual IJSProperty* HasProperty( const CStrW& PropertyName ) = 0;
// Retrieve the value of a property (returning false if that property is not defined)
virtual bool GetProperty( JSContext* cx, const CStrW& PropertyName, jsval* vp ) = 0;
// Add a property (with immediate value)
virtual void AddProperty( const CStrW& PropertyName, jsval Value ) = 0;
virtual void AddProperty( const CStrW& PropertyName, const CStrW& Value ) = 0;
inline IJSObject() {}
};
template<typename T, bool ReadOnly = false> class CJSObject;
template<typename T, bool ReadOnly> class CJSProperty : public IJSProperty
{
T IJSObject::*m_Data;
public:
CJSProperty( T IJSObject::*Data )
{
m_Data = Data;
}
jsval Get( JSContext* UNUSED(cx), IJSObject* owner )
{
return( ToJSVal( owner->*m_Data ) );
}
void Set( JSContext* cx, IJSObject* owner, jsval Value )
{
if( !ReadOnly )
ToPrimitive( cx, Value, owner->*m_Data );
}
};
#ifdef ALLOW_NONSHARED_NATIVES
template<typename T, bool ReadOnly> class CJSNonsharedProperty : public IJSProperty
{
T* m_Data;
public:
CJSNonsharedProperty( T* Data )
{
m_Data = Data;
}
jsval Get( JSContext* UNUSED(cx), IJSObject* UNUSED(owner) )
{
return( ToJSVal( *m_Data ) );
}
void Set( JSContext* cx, IJSObject* UNUSED(owner), jsval Value )
{
if( !ReadOnly )
ToPrimitive( cx, Value, *m_Data );
}
};
#endif /* ALLOW_NONSHARED_NATIVES */
class CJSFunctionProperty : public IJSProperty
{
// Function on Owner to get the value
IJSObject::GetFn m_Getter;
// Function on Owner to set the value
IJSObject::SetFn m_Setter;
public:
CJSFunctionProperty( IJSObject::GetFn Getter, IJSObject::SetFn Setter )
{
m_Getter = Getter;
m_Setter = Setter;
// Must at least be able to read
debug_assert( m_Getter );
}
jsval Get( JSContext* cx, IJSObject* obj )
{
return( (obj->*m_Getter)(cx) );
}
void Set( JSContext* cx, IJSObject* obj, jsval value )
{
if( m_Setter )
(obj->*m_Setter)( cx, value );
}
};
class CJSValProperty : public IJSProperty
{
template<typename Q, bool ReadOnly> friend class CJSObject;
jsval m_Data;
public:
CJSValProperty( jsval Data )
{
m_Data = Data;
Root();
}
~CJSValProperty()
{
Uproot();
}
void Root()
{
if( JSVAL_IS_GCTHING( m_Data ) )
JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_Data, "ScriptableObjectProperty" );
}
void Uproot()
{
if( JSVAL_IS_GCTHING( m_Data ))
JS_RemoveRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
}
jsval Get( JSContext* UNUSED(cx), IJSObject* UNUSED(object))
{
return( m_Data );
}
void Set( JSContext* UNUSED(cx), IJSObject* UNUSED(owner), jsval value )
{
Uproot();
m_Data = value;
Root();
}
};
// Wrapper around native functions that are attached to CJSObjects
template<typename T, bool ReadOnly, typename RType, RType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )> class CNativeFunction
{
public:
static JSBool JSFunction( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
T* Native = ToNative<T>( cx, obj );
if( !Native )
return( JS_TRUE );
*rval = ToJSVal<RType>( (Native->*NativeFunction)( cx, argc, argv ) );
return( JS_TRUE );
}
};
// Special case for void functions
template<typename T, bool ReadOnly, void (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
class CNativeFunction<T, ReadOnly, void, NativeFunction>
{
public:
static JSBool JSFunction( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* UNUSED(rval) )
{
T* Native = ToNative<T>( cx, obj );
if( !Native )
return( JS_TRUE );
(Native->*NativeFunction)( cx, argc, argv );
return( JS_TRUE );
}
};
template<typename T, bool ReadOnly> class CJSObject : public IJSObject
{
// This object
JSObject* m_JS;
protected:
// The properties defined by the engine
static PropertyTable m_NativeProperties;
#ifdef ALLOW_NONSHARED_NATIVES
PropertyTable m_NonsharedProperties;
#endif
// Properties added by script
PropertyTable m_ScriptProperties;
// Whether native code is responsible for managing this object.
// Script constructors should clear this *BEFORE* creating a JS
// mirror (otherwise it'll be rooted).
bool m_EngineOwned;
public:
// Property getters and setters
typedef jsval (T::*TGetFn)( JSContext* );
typedef void (T::*TSetFn)( JSContext*, jsval value );
static JSClass JSI_class;
static void ScriptingInit( const char* ClassName, JSNative Constructor = NULL, uintN ConstructorMinArgs = 0 )
{
JSFunctionSpec* JSI_methods = new JSFunctionSpec[ m_Methods.size() + 1 ];
size_t MethodID;
for( MethodID = 0; MethodID < m_Methods.size(); MethodID++ )
JSI_methods[MethodID] = m_Methods[MethodID];
JSI_methods[MethodID].name = 0;
JSI_class.name = ClassName;
g_ScriptingHost.DefineCustomObjectType( &JSI_class, Constructor, ConstructorMinArgs, JSI_props, JSI_methods, NULL, NULL );
delete[]( JSI_methods );
atexit( ScriptingShutdown );
}
static void ScriptingShutdown()
{
PropertyTable::iterator it;
for( it = m_NativeProperties.begin(); it != m_NativeProperties.end(); it++ )
delete( it->second );
}
// JS Property access
bool GetProperty( JSContext* cx, const CStrW& PropertyName, jsval* vp )
{
IJSProperty* Property = HasProperty( PropertyName );
if( Property )
*vp = Property->Get( cx, this );
return( true );
}
void SetProperty( JSContext* cx, const CStrW& PropertyName, jsval* vp )
{
if( !ReadOnly )
{
IJSProperty* prop = HasProperty( PropertyName );
if( prop )
{
// Already exists
prop->Set( cx, this, *vp );
}
else
{
// Need to add it
AddProperty( PropertyName, *vp );
}
}
}
IJSProperty* HasProperty( const CStrW& PropertyName )
{
PropertyTable::iterator it;
// Engine-defined properties take precedence
it = m_NativeProperties.find( PropertyName );
if( it != m_NativeProperties.end() )
return( it->second );
// Next are the object-local engine-defined properties
// (if they're compiled in)
#ifdef ALLOW_NONSHARED_NATIVES
it = m_NonsharedProperties.find( PropertyName );
if( it != m_NonsharedProperties.end() )
return( it->second );
#endif
// Then check in script properties
it = m_ScriptProperties.find( PropertyName );
if( it != m_ScriptProperties.end() )
return( it->second );
// Otherwise not found
return( NULL );
}
void AddProperty( const CStrW& PropertyName, jsval Value )
{
debug_assert( !HasProperty( PropertyName ) );
CJSValProperty* newProp = new CJSValProperty( Value );
m_ScriptProperties[PropertyName] = newProp;
}
void AddProperty( const CStrW& PropertyName, const CStrW& Value )
{
AddProperty( PropertyName, JSParseString( Value ) );
}
static void AddProperty( const CStrW& PropertyName, TGetFn Getter, TSetFn Setter = NULL )
{
m_NativeProperties[PropertyName] = new CJSFunctionProperty( (GetFn)Getter, (SetFn)Setter );
}
template<typename ReturnType, ReturnType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
static void AddMethod( const char* Name, uintN MinArgs )
{
JSFunctionSpec FnInfo = { Name, CNativeFunction<T, ReadOnly, ReturnType, NativeFunction>::JSFunction, (uint8)MinArgs, 0, 0 };
m_Methods.push_back( FnInfo );
}
template<typename PropType> static void AddProperty( const CStrW& PropertyName, PropType T::*Native, bool PropReadOnly = ReadOnly )
{
IJSProperty* prop;
if( PropReadOnly )
{
prop = new CJSProperty<PropType, true>( (PropType IJSObject::*)Native );
}
else
{
prop = new CJSProperty<PropType, ReadOnly>( (PropType IJSObject::*)Native );
}
m_NativeProperties[PropertyName] = prop;
}
#ifdef ALLOW_NONSHARED_NATIVES
template<typename PropType> void AddLocalProperty( const CStrW& PropertyName, PropType* Native, bool PropReadOnly = ReadOnly )
{
IJSProperty* prop;
if( PropReadOnly )
{
prop = new CJSNonsharedProperty<PropType, true>( Native );
}
else
prop = new CJSNonsharedProperty<PropType, ReadOnly>( Native );
m_NonsharedProperties[PropertyName] = prop;
}
#endif
// Object operations
JSObject* GetScript()
{
if( !m_JS )
CreateScriptObject();
return( m_JS );
}
// Creating and releasing script objects is done automatically most of the time, but you
// can do it explicitly.
void CreateScriptObject()
{
if( !m_JS )
{
m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
if( m_EngineOwned )
JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_JS, JSI_class.name );
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, (T*)this );
}
}
void ReleaseScriptObject()
{
if( m_JS )
{
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL );
if( m_EngineOwned )
JS_RemoveRoot( g_ScriptingHost.GetContext(), &m_JS );
m_JS = NULL;
}
}
//
// Functions and data that must be provided to JavaScript
//
private:
static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
T* Instance = ToNative<T>( cx, obj );
if( !Instance )
return( JS_TRUE );
CStrW PropName = g_ScriptingHost.ValueToUCString( id );
if( !Instance->GetProperty( cx, PropName, vp ) )
return( JS_TRUE );
return( JS_TRUE );
}
static JSBool JSSetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
T* Instance = ToNative<T>( cx, obj );
if( !Instance )
return( JS_TRUE );
CStrW PropName = g_ScriptingHost.ValueToUCString( id );
Instance->SetProperty( cx, PropName, vp );
return( JS_TRUE );
}
static void DefaultFinalize( JSContext *cx, JSObject *obj )
{
T* Instance = ToNative<T>( cx, obj );
if( !Instance || Instance->m_EngineOwned )
return;
delete( Instance );
JS_SetPrivate( cx, obj, NULL );
}
static JSPropertySpec JSI_props[];
static std::vector<JSFunctionSpec> m_Methods;
public:
// Standard constructors/destructors
CJSObject()
{
m_JS = NULL;
m_EngineOwned = true;
}
virtual ~CJSObject()
{
Shutdown();
}
void Shutdown()
{
PropertyTable::iterator it;
for( it = m_ScriptProperties.begin(); it != m_ScriptProperties.end(); it++ )
delete( it->second );
m_ScriptProperties.clear();
ReleaseScriptObject();
#ifdef ALLOW_NONSHARED_NATIVES
for( it = m_NonsharedProperties.begin(); it != m_NonsharedProperties.end(); it++ )
delete( it->second );
m_NonsharedProperties.clear();
#endif
}
};
template<typename T, bool ReadOnly> JSClass CJSObject<T, ReadOnly>::JSI_class = {
NULL, JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
JSGetProperty, JSSetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, DefaultFinalize,
NULL, NULL, NULL, NULL
};
template<typename T, bool ReadOnly> JSPropertySpec CJSObject<T, ReadOnly>::JSI_props[] = {
{ 0 },
};
template<typename T, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, ReadOnly>::m_Methods;
template<typename T, bool ReadOnly> typename CJSObject<T, ReadOnly>::PropertyTable CJSObject<T, ReadOnly>::m_NativeProperties;
#endif