diff --git a/source/scripting/EventTypes.h b/source/scripting/EventTypes.h new file mode 100644 index 0000000000..1c9cfd7507 --- /dev/null +++ b/source/scripting/EventTypes.h @@ -0,0 +1,33 @@ +// EventTypes.h +// Script event declarations + +enum EEventType +{ + // Entity events + EVENT_INITIALIZE = 0, + EVENT_TICK, + EVENT_ATTACK, + EVENT_GATHER, + EVENT_DAMAGE, + EVENT_TARGET_CHANGED, + EVENT_PREPARE_ORDER, + EVENT_ORDER_TRANSITION, + EVENT_LAST, + // General events + EVENT_GAME_START = 0, + EVENT_GAME_TICK, + EVENT_SELECTION_CHANGED, +}; + +// Only used for entity events... +static const wchar_t* EventNames[] = +{ + /* EVENT_INITIALIZE */ L"onInitialize", + /* EVENT_TICK */ L"onTick", + /* EVENT_ATTACK */ L"onAttack", /* This unit is the one doing the attacking... */ + /* EVENT_GATHER */ L"onGather", /* This unit is the one doing the gathering... */ + /* EVENT_DAMAGE */ L"onTakesDamage", + /* EVENT_TARGET_CHANGED */ L"onTargetChanged", /* If this unit is selected and the mouseover object changes */ + /* EVENT_PREPARE_ORDER */ L"onPrepareOrder", /* To check if a unit can execute a given order */ + /* EVENT_ORDER_TRANSITION */ L"onOrderTransition" /* When we change orders (sometimes...) */ +}; \ No newline at end of file diff --git a/source/scripting/GameEvents.h b/source/scripting/GameEvents.h new file mode 100644 index 0000000000..0ab3ab8b14 --- /dev/null +++ b/source/scripting/GameEvents.h @@ -0,0 +1,37 @@ +// GameEvents.h + +// A class that exists to let scripts know when important things happen +// in the game. + +#ifndef GAME_EVENTS_INCLUDED +#define GAME_EVENTS_INCLUDED + +#include "DOMEvent.h" + +class CGameEvents : public IEventTarget, public Singleton +{ + // Game events don't really run on an object + JSObject* GetScriptExecContext( IEventTarget* target ) { return( g_ScriptingHost.GetGlobalObject() ); } + + // Some events + class CEventSelectionChanged : public CScriptEvent + { + bool m_CausedByPlayer; + public: + CEventSelectionChanged( bool CausedByPlayer ) : CScriptEvent( L"selectionChanged", EVENT_SELECTION_CHANGED, false ) + { + AddLocalProperty( L"byPlayer", &m_CausedByPlayer, true ); + } + }; + +public: + void FireSelectionChanged( bool CausedByPlayer ) + { + CEventSelectionChanged evt( CausedByPlayer ); + DispatchEvent( &evt ); + } +}; + +#define g_JSGameEvents CGameEvents::GetSingleton() + +#endif \ No newline at end of file diff --git a/source/scripting/ScriptableComplex.h b/source/scripting/ScriptableComplex.h new file mode 100644 index 0000000000..e838f1ea23 --- /dev/null +++ b/source/scripting/ScriptableComplex.h @@ -0,0 +1,1036 @@ +// ScriptableComplex.h +// +// The version of CJSObject<> that retains the ability to use inheritance +// in its objects. Shouldn't be used any more for anything but entity code. + +#include "scripting/ScriptingHost.h" +#include "JSConversions.h" + +#include + +#ifndef SCRIPTABLE_COMPLEX_INCLUDED +#define SCRIPTABLE_COMPLEX_INCLUDED + +class IJSComplex; + +class IJSComplexProperty +{ +public: + + bool m_AllowsInheritance; + bool m_Inherited; + bool m_Intrinsic; + + // This is to make sure that all the fields are initialized at construction + inline IJSComplexProperty(): + m_AllowsInheritance(true), + m_Inherited(true), + m_Intrinsic(true) + {} + + virtual jsval Get( JSContext* cx, IJSComplex* owner ) = 0; + virtual void Set( JSContext* cx, IJSComplex* owner, jsval Value ) = 0; + + // Copies the data directly out of a parent property + // Warning: Don't use if you're not certain the properties are not of the same type. + virtual void ImmediateCopy( IJSComplex* CopyTo, IJSComplex* CopyFrom, IJSComplexProperty* CopyProperty ) = 0; + + jsval Get( IJSComplex* owner ) { return( Get( g_ScriptingHost.GetContext(), owner ) ); } + void Set( IJSComplex* owner, jsval Value ) { return( Set( g_ScriptingHost.GetContext(), owner, Value ) ); } + + virtual ~IJSComplexProperty() {} +}; + +class IJSComplex +{ + // Make copy constructor and assignment operator private - since copying of + // these objects is unsafe unless done specially. + // These will never be implemented (they are, after all, here to *prevent* + // copying) + IJSComplex(const IJSComplex &other); + IJSComplex& operator=(const IJSComplex &other); + +public: + typedef STL_HASH_MAP PropertyTable; + typedef std::vector InheritorsList; + typedef std::set StringTable; + typedef std::pair IteratorState; + + // Used for freshen/update + typedef void (IJSComplex::*NotifyFn)(); + + // Property getters and setters + typedef jsval (IJSComplex::*GetFn)(); + typedef void (IJSComplex::*SetFn)( jsval ); + + // Properties of this object + PropertyTable m_Properties; + + // Parent object + IJSComplex* m_Parent; + + // Objects that inherit from this + InheritorsList m_Inheritors; + + // Set the base, and rebuild + void SetBase( IJSComplex* m_Parent ); + + // Rebuild any intrinsic (mapped-to-C++-variable) properties + virtual void Rebuild() = 0; + + // Check for a property + virtual IJSComplexProperty* HasProperty( CStrW PropertyName ) = 0; + + // Get all properties of an object + virtual void FillEnumerateSet( IteratorState* it, CStrW* PropertyRoot = NULL ) = 0; + + // Retrieve the value of a property (returning false if that property is not defined) + virtual bool GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) = 0; + + // Add a property (with immediate value) + virtual void AddProperty( CStrW PropertyName, jsval Value ) = 0; + virtual void AddProperty( CStrW PropertyName, CStrW Value ) = 0; + + inline IJSComplex() {} +}; + +template class CJSComplex; + +template class CJSComplexPropertyAccessor +{ + T* m_Owner; + CStrW m_PropertyRoot; + template friend class CJSComplex; + +public: + CJSComplexPropertyAccessor( T* Owner, CStrW PropertyRoot ) + { + m_Owner = Owner; + m_PropertyRoot = PropertyRoot; + } + static JSObject* CreateAccessor( JSContext* cx, T* Owner, CStrW PropertyRoot ) + { + JSObject* Accessor = JS_NewObject( cx, &JSI_Class, NULL, NULL ); + JS_SetPrivate( cx, Accessor, new CJSComplexPropertyAccessor( Owner, PropertyRoot ) ); + + return( Accessor ); + } + static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) + { + CJSComplexPropertyAccessor* Instance = (CJSComplexPropertyAccessor*)JS_GetPrivate( cx, obj ); + if( !Instance ) return( JS_TRUE ); + + CStrW PropName = Instance->m_PropertyRoot + CStrW( L"." ) + g_ScriptingHost.ValueToUCString( id ); + + Instance->m_Owner->GetProperty( cx, PropName, vp ); + + return( JS_TRUE ); + } + static JSBool JSSetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) + { + CJSComplexPropertyAccessor* Instance = (CJSComplexPropertyAccessor*)JS_GetPrivate( cx, obj ); + if( !Instance ) return( JS_TRUE ); + + CStrW PropName = g_ScriptingHost.ValueToUCString( id ); + + Instance->m_Owner->SetProperty( cx, Instance->m_PropertyRoot + CStrW( L"." ) + PropName, vp ); + + return( JS_TRUE ); + } + static JSBool JSEnumerate( JSContext* cx, JSObject* obj, JSIterateOp enum_op, jsval* statep, jsid *idp ) + { + IJSComplex::IteratorState* it; + + switch( enum_op ) + { + case JSENUMERATE_INIT: + { + CJSComplexPropertyAccessor* Instance = (CJSComplexPropertyAccessor*)JS_GetPrivate( cx, obj ); + + it = new IJSComplex::IteratorState; + + if( Instance ) + { + size_t rlen = Instance->m_PropertyRoot.Length(); + + IJSComplex::PropertyTable::iterator iit; + for( iit = T::m_IntrinsicProperties.begin(); iit != T::m_IntrinsicProperties.end(); iit++ ) + if( ( iit->first.Length() > rlen ) && ( iit->first.Left( rlen ) == Instance->m_PropertyRoot ) && ( iit->first[rlen] == '.' ) ) + it->first.insert( iit->first.GetSubstring( rlen + 1, iit->first.Length() - rlen - 1 ).BeforeFirst( L"." ) ); + + + Instance->m_Owner->FillEnumerateSet( it, &( Instance->m_PropertyRoot ) ); + } + it->second = it->first.begin(); + + *statep = PRIVATE_TO_JSVAL( it ); + + if( idp ) + *idp = INT_TO_JSVAL( it->first.size() ); + + return( JS_TRUE ); + } + case JSENUMERATE_NEXT: + it = (IJSComplex::IteratorState*)JSVAL_TO_PRIVATE( *statep ); + if( it->second == it->first.end() ) + { + delete( it ); + *statep = JSVAL_NULL; + return( JS_TRUE ); + } + + // I think this is what I'm supposed to do... (cheers, Philip) + if( !JS_ValueToId( g_ScriptingHost.GetContext(), ToJSVal( *( it->second ) ), idp ) ) + return( JS_FALSE ); + + (it->second)++; + + *statep = PRIVATE_TO_JSVAL( it ); + return( JS_TRUE ); + case JSENUMERATE_DESTROY: + it = (IJSComplex::IteratorState*)JSVAL_TO_PRIVATE( *statep ); + delete( it ); + *statep = JSVAL_NULL; + return( JS_TRUE ); + } + return( JS_FALSE ); + } + static JSBool JSPrimitive( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) + { + CJSComplexPropertyAccessor* Instance = (CJSComplexPropertyAccessor*)JS_GetPrivate( cx, obj ); + if( !Instance ) return( JS_TRUE ); + + // Check all along the inheritance tree + // Possible optimization: Store the hashed value over the lookups + IJSComplex* Target = Instance->m_Owner; + IJSComplexProperty* Property; + + while( Target ) + { + Property = Target->HasProperty( Instance->m_PropertyRoot ); + if( Property ) + { + *rval = Property->Get( cx, Target ); + break; + } + Target = Target->m_Parent; + } + + return( JS_TRUE ); + } + static JSBool JSToString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) + { + CJSComplexPropertyAccessor* Instance = (CJSComplexPropertyAccessor*)JS_GetPrivate( cx, obj ); + if( !Instance ) return( JS_TRUE ); + + // Check all along the inheritance tree + // TODO: Optimization: Store the hashed value over the lookups + IJSComplex* Target = Instance->m_Owner; + IJSComplexProperty* Property; + JSString* str; + + while( Target ) + { + Property = Target->HasProperty( Instance->m_PropertyRoot ); + if( Property ) + { + str = JS_ValueToString( cx, Property->Get( cx, Target ) ); + break; + } + Target = Target->m_Parent; + } + + *rval = STRING_TO_JSVAL( str ); + + return( JS_TRUE ); + } + static JSClass JSI_Class; + static void ScriptingInit() + { + JSFunctionSpec JSI_methods[] = { { "valueOf", JSPrimitive, 0, 0, 0 }, { "toString", JSToString, 0, 0, 0 }, { 0 } }; + JSPropertySpec JSI_props[] = { { 0 } }; + + g_ScriptingHost.DefineCustomObjectType( &JSI_Class, NULL, 0, JSI_props, JSI_methods, NULL, NULL ); + } +}; + +template JSClass CJSComplexPropertyAccessor::JSI_Class = { + "Property", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_ENUMERATE, + JS_PropertyStub, JS_PropertyStub, + JSGetProperty, JSSetProperty, + (JSEnumerateOp)JSEnumerate, JS_ResolveStub, + JS_ConvertStub, JS_FinalizeStub, + NULL, NULL, NULL, NULL +}; + + +template class CJSSharedProperty : public IJSComplexProperty +{ + T IJSComplex::*m_Data; + + // Function on Owner to call after value is changed + IJSComplex::NotifyFn m_Update; + + // Function on Owner to call before reading or writing the value + IJSComplex::NotifyFn m_Freshen; + +public: + CJSSharedProperty( T IJSComplex::*Data, bool AllowsInheritance = false, IJSComplex::NotifyFn Update = NULL, IJSComplex::NotifyFn Freshen = NULL ) + { + m_Data = Data; + m_AllowsInheritance = AllowsInheritance; + m_Update = Update; + m_Freshen = Freshen; + m_Intrinsic = true; + m_Inherited = true; + } + jsval Get( JSContext* cx, IJSComplex* owner ) + { + if( m_Freshen ) (owner->*m_Freshen)(); + return( ToJSVal( owner->*m_Data ) ); + } + void ImmediateCopy( IJSComplex* CopyTo, IJSComplex* CopyFrom, IJSComplexProperty* CopyProperty ) + { + debug_warn( "Inheritance not supported for CJSSharedProperties" ); + } + void Set( JSContext* cx, IJSComplex* owner, jsval Value ) + { + if( !ReadOnly ) + { + if( m_Freshen ) (owner->*m_Freshen)(); + if( ToPrimitive( cx, Value, owner->*m_Data ) ) + if( m_Update ) (owner->*m_Update)(); + } + } + +}; + +template class CJSComplexProperty : public IJSComplexProperty +{ + T* m_Data; + + // Function on Owner to call after value is changed + IJSComplex::NotifyFn m_Update; + + // Function on Owner to call before reading or writing the value + IJSComplex::NotifyFn m_Freshen; + +public: + CJSComplexProperty( T* Data, bool AllowsInheritance = false, IJSComplex::NotifyFn Update = NULL, IJSComplex::NotifyFn Freshen = NULL ) + { + m_Data = Data; + m_AllowsInheritance = AllowsInheritance; + m_Update = Update; + m_Freshen = Freshen; + m_Intrinsic = true; + } + jsval Get( JSContext* cx, IJSComplex* owner ) + { + if( m_Freshen ) (owner->*m_Freshen)(); + return( ToJSVal( *m_Data ) ); + } + void ImmediateCopy( IJSComplex* CopyTo, IJSComplex* CopyFrom, IJSComplexProperty* CopyProperty ) + { + *m_Data = *( ( (CJSComplexProperty*)CopyProperty )->m_Data ); + } + void Set( JSContext* cx, IJSComplex* owner, jsval Value ) + { + if( !ReadOnly ) + { + if( m_Freshen ) (owner->*m_Freshen)(); + if( ToPrimitive( cx, Value, *m_Data ) ) + if( m_Update ) (owner->*m_Update)(); + } + } + +}; + +class CJSReflector +{ + template friend class CJSComplex; + JSObject* m_JSAccessor; +}; + +class CJSDynamicComplexProperty : public IJSComplexProperty +{ + template friend class CJSComplex; + + JSObject* m_JSAccessor; +public: + CJSDynamicComplexProperty() + { + m_JSAccessor = NULL; + m_Intrinsic = false; + m_Inherited = false; + } +}; + +class CJSValComplexProperty : public CJSDynamicComplexProperty +{ + template friend class CJSComplex; + + jsval m_Data; + +public: + CJSValComplexProperty( jsval Data, bool Inherited ) + { + m_Inherited = Inherited; + m_Data = Data; + Root(); + } + ~CJSValComplexProperty() + { + Uproot(); + } + void Root() + { + if( JSVAL_IS_GCTHING( m_Data ) ) +#ifndef NDEBUG + JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_Data, "jsval property" ); +#else + JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_Data ); +#endif + } + void Uproot() + { + if( JSVAL_IS_GCTHING( m_Data ) ) + JS_RemoveRoot( g_ScriptingHost.GetContext(), (void*)&m_Data ); + } + jsval Get( JSContext* cx, IJSComplex* owner ) + { + return( m_Data ); + } + void Set( JSContext* cx, IJSComplex* owner, jsval Value ) + { + Uproot(); + m_Data = Value; + Root(); + } + void ImmediateCopy( IJSComplex* CopyTo, IJSComplex* CopyFrom, IJSComplexProperty* CopyProperty ) + { + assert( 0 && "ImmediateCopy called on a CJSValComplexProperty (something's gone wrong with the inheritance on this object)" ); + } +}; + +class CJSFunctionComplexProperty : public IJSComplexProperty +{ + // Function on Owner to get the value + IJSComplex::GetFn m_Getter; + + // Function on Owner to set the value + IJSComplex::SetFn m_Setter; + +public: + CJSFunctionComplexProperty( IJSComplex::GetFn Getter, IJSComplex::SetFn Setter ) + { + m_Inherited = false; + m_Intrinsic = true; + m_Getter = Getter; + m_Setter = Setter; + // Must at least be able to read + assert( m_Getter ); + } + jsval Get( JSContext* cx, IJSComplex* owner ) + { + return( (owner->*m_Getter)() ); + } + void Set( JSContext* cx, IJSComplex* owner, jsval Value ) + { + if( m_Setter ) + (owner->*m_Setter)( Value ); + } + void ImmediateCopy( IJSComplex* CopyTo, IJSComplex* CopyFrom, IJSComplexProperty* CopyProperty ) + { + assert( 0 && "ImmediateCopy called on a property wrapping getter/setter functions (something's gone wrong with the inheritance for this object)" ); + } +}; + + +// Wrapper around native functions that are attached to CJSComplexs + +template class CNativeComplexFunction +{ +public: + static JSBool JSFunction( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) + { + T* Native = ToNative( cx, obj ); + if( !Native ) + return( JS_TRUE ); + + *rval = ToJSVal( (Native->*NativeFunction)( cx, argc, argv ) ); + + return( JS_TRUE ); + } +}; + +template class CJSComplex : public IJSComplex +{ + typedef STL_HASH_MAP ReflectorTable; + template friend class CJSComplexPropertyAccessor; + JSObject* m_JS; + + std::vector m_Watches; + + ReflectorTable m_Reflectors; + +public: + static JSClass JSI_class; + + // 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; + + // JS Property access + bool GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ); + void SetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) + { + if( !ReadOnly ) + { + IJSComplexProperty* prop = HasProperty( PropertyName ); + + if( prop ) + { + // Already exists + WatchNotify( cx, PropertyName, vp ); + prop->Set( cx, this, *vp ); + + prop->m_Inherited = false; + + // If it's a C++ property, reflect this change in objects that inherit this. + if( prop->m_AllowsInheritance && prop->m_Intrinsic ) + { + InheritorsList UpdateSet( m_Inheritors ); + + while( !UpdateSet.empty() ) + { + IJSComplex* UpdateObj = UpdateSet.back(); + UpdateSet.pop_back(); + IJSComplexProperty* UpdateProp = UpdateObj->HasProperty( PropertyName ); + // Property must exist, also be a C++ property, and not have its value specified. + if( UpdateProp && UpdateProp->m_Intrinsic && UpdateProp->m_Inherited ) + { + UpdateProp->Set( cx, this, *vp ); + InheritorsList::iterator it2; + for( it2 = UpdateObj->m_Inheritors.begin(); it2 != UpdateObj->m_Inheritors.end(); it2++ ) + UpdateSet.push_back( *it2 ); + } + } + } + } + else + { + // Need to add it + WatchNotify( cx, PropertyName, vp ); + AddProperty( PropertyName, *vp ); + } + } + } + void WatchNotify( JSContext* cx, CStrW PropertyName, jsval* newval ) + { + if( m_Watches.empty() ) return; + + jsval oldval = JSVAL_VOID; + GetProperty( cx, PropertyName, &oldval ); + + jsval args[3] = { ToJSVal( PropertyName ), oldval, *newval }; + + std::vector::iterator it; + for( it = m_Watches.begin(); it != m_Watches.end(); it++ ) + if( it->Run( GetScript(), newval, 3, args ) ) + args[2] = *newval; + } + // + // Functions that must be provided to JavaScript + // + static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) + { + T* Instance = ToNative( 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( cx, obj ); + if( !Instance ) + return( JS_TRUE ); + + CStrW PropName = g_ScriptingHost.ValueToUCString( id ); + + Instance->SetProperty( cx, PropName, vp ); + + return( JS_TRUE ); + } + static JSBool JSEnumerate( JSContext* cx, JSObject* obj, JSIterateOp enum_op, jsval* statep, jsid *idp ) + { + IteratorState* it; + + switch( enum_op ) + { + case JSENUMERATE_INIT: + { + T* Instance = ToNative( cx, obj ); + + it = new IteratorState; + if( Instance ) + { + PropertyTable::iterator iit; + for( iit = T::m_IntrinsicProperties.begin(); iit != T::m_IntrinsicProperties.end(); iit++ ) + it->first.insert( iit->first ); + + Instance->FillEnumerateSet( it ); + } + + it->second = it->first.begin(); + *statep = PRIVATE_TO_JSVAL( it ); + + if( idp ) + *idp = INT_TO_JSVAL( it->first.size() ); + + return( JS_TRUE ); + } + case JSENUMERATE_NEXT: + it = (IteratorState*)JSVAL_TO_PRIVATE( *statep ); + if( it->second == it->first.end() ) + { + delete( it ); + *statep = JSVAL_NULL; + return( JS_TRUE ); + } + + // I think this is what I'm supposed to do... (cheers, Philip) + if( !JS_ValueToId( g_ScriptingHost.GetContext(), ToJSVal( *( it->second ) ), idp ) ) + return( JS_FALSE ); + + (it->second)++; + + *statep = PRIVATE_TO_JSVAL( it ); + return( JS_TRUE ); + case JSENUMERATE_DESTROY: + it = (IteratorState*)JSVAL_TO_PRIVATE( *statep ); + delete( it ); + *statep = JSVAL_NULL; + return( JS_TRUE ); + } + return( JS_FALSE ); + } + static JSBool SetWatchAll( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) + { + T* Native = ToNative( cx, obj ); + if( !Native ) + return( JS_TRUE ); + + assert( argc >= 1 ); + + CScriptObject watch( argv[0] ); + std::vector::iterator it; + for( it = Native->m_Watches.begin(); it != Native->m_Watches.end(); it++ ) + if( *it == watch ) + { + *rval = JSVAL_FALSE; + return( JS_TRUE ); + } + + Native->m_Watches.push_back( watch ); + *rval = JSVAL_TRUE; + return( JS_TRUE ); + } + + static JSBool UnWatchAll( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) + { + T* Native = ToNative( cx, obj ); + if( !Native ) + return( JS_TRUE ); + + if( argc >= 1 ) + { + CScriptObject watch( argv[0] ); + std::vector::iterator it; + for( it = Native->m_Watches.begin(); it != Native->m_Watches.end(); it++ ) + if( *it == watch ) + { + Native->m_Watches.erase( it ); + *rval = JSVAL_TRUE; + return( JS_TRUE ); + } + + *rval = JSVAL_FALSE; + } + else + { + Native->m_Watches.clear(); + *rval = JSVAL_TRUE; + } + return( JS_TRUE ); + } + + static void ScriptingInit( const char* ClassName, JSNative Constructor = NULL, uintN ConstructorMinArgs = 0 ) + { + JSFunctionSpec* JSI_methods = new JSFunctionSpec[ m_Methods.size() + 3 ]; + unsigned int MethodID; + for( MethodID = 0; MethodID < m_Methods.size(); MethodID++ ) + JSI_methods[MethodID] = m_Methods[MethodID]; + + JSFunctionSpec watchAll = { "watchAll", SetWatchAll, 1, 0, 0 }; + JSI_methods[MethodID] = watchAll; + JSFunctionSpec unwatchAll = { "unwatchAll", UnWatchAll, 1, 0, 0 }; + JSI_methods[MethodID + 1] = unwatchAll; + JSI_methods[MethodID + 2].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_IntrinsicProperties.begin(); it != m_IntrinsicProperties.end(); it++ ) + delete( it->second ); + } + static void DefaultFinalize( JSContext *cx, JSObject *obj ) + { + T* Instance = ToNative( cx, obj ); + if( !Instance || Instance->m_EngineOwned ) + return; + + delete( Instance ); + JS_SetPrivate( cx, obj, NULL ); + } + +public: + + 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 ) + { +#ifndef NDEBUG // Name the GC roots something more useful than 'ScriptableObject.h' + JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_JS, JSI_class.name ); +#else + JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_JS ); +#endif + } + 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; + } + } +private: + static JSPropertySpec JSI_props[]; + static std::vector m_Methods; + static PropertyTable m_IntrinsicProperties; + + +public: + CJSComplex() + { + m_Parent = NULL; + m_JS = NULL; + m_EngineOwned = true; + } + virtual ~CJSComplex() + { + Shutdown(); + } + void Shutdown() + { + PropertyTable::iterator it; + for( it = m_Properties.begin(); it != m_Properties.end(); it++ ) + { + if( !it->second->m_Intrinsic ) + { + CJSDynamicComplexProperty* extProp = (CJSValComplexProperty*)it->second; + if( extProp->m_JSAccessor ) + { + CJSComplexPropertyAccessor< CJSComplex >* accessor = (CJSComplexPropertyAccessor< CJSComplex >*)JS_GetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor ); + assert( accessor ); + delete( accessor ); + JS_SetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor, NULL ); + JS_RemoveRoot( g_ScriptingHost.GetContext(), &( extProp->m_JSAccessor ) ); + } + } + delete( it->second ); + } + + + ReflectorTable::iterator it_a; + for( it_a = m_Reflectors.begin(); it_a != m_Reflectors.end(); it_a++ ) + { + CJSComplexPropertyAccessor< CJSComplex >* accessor = (CJSComplexPropertyAccessor< CJSComplex >*)JS_GetPrivate( g_ScriptingHost.GetContext(), it_a->second->m_JSAccessor ); + assert( accessor ); + delete( accessor ); + JS_SetPrivate( g_ScriptingHost.GetContext(), it_a->second->m_JSAccessor, NULL ); + JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it_a->second->m_JSAccessor ) ); + delete( it_a->second ); + } + ReleaseScriptObject(); + } + void SetBase( IJSComplex* Parent ) + { + if( m_Parent ) + { + // Remove this from the list of our parent's inheritors + InheritorsList::iterator it; + for( it = m_Parent->m_Inheritors.begin(); it != m_Parent->m_Inheritors.end(); it++ ) + if( (*it) == this ) + { + m_Parent->m_Inheritors.erase( it ); + break; + } + } + m_Parent = Parent; + if( m_Parent ) + { + // Place this in the list of our parent's inheritors + m_Parent->m_Inheritors.push_back( this ); + Rebuild(); + } + } + void Rebuild() + { + PropertyTable::iterator it; + // For each intrinsic property we have, + for( it = m_Properties.begin(); it != m_Properties.end(); it++ ) + { + if( !it->second->m_Intrinsic || !it->second->m_Inherited ) + continue; + + // Attempt to locate it in the parent + IJSComplexProperty* cp = m_Parent->HasProperty( it->first ); + + // If it doesn't have it, we've inherited from an object of a different type + // This isn't allowed at the moment; but I don't have an totally convincing + // reason for forbidding it entirely. Mind, I can't think of any use for it, + // either. + // If it can be inherited, inherit it. + if( cp && cp->m_AllowsInheritance ) + { + assert( cp->m_Intrinsic ); + it->second->ImmediateCopy( this, m_Parent, cp ); + } + } + // Do the same for the shared properties table, too + for( it = m_IntrinsicProperties.begin(); it != m_IntrinsicProperties.end(); it++ ) + { + if( !it->second->m_Inherited ) + continue; + + IJSComplexProperty* cp = m_Parent->HasProperty( it->first ); + + if( cp && cp->m_AllowsInheritance ) + { + assert( cp->m_Intrinsic ); + it->second->ImmediateCopy( this, m_Parent, cp ); + } + } + + // Now recurse. + InheritorsList::iterator c; + for( c = m_Inheritors.begin(); c != m_Inheritors.end(); c++ ) + (*c)->Rebuild(); + + } + IJSComplexProperty* HasProperty( CStrW PropertyName ) + { + PropertyTable::iterator it; + it = T::m_IntrinsicProperties.find( PropertyName ); + if( it != T::m_IntrinsicProperties.end() ) + return( it->second ); + + it = m_Properties.find( PropertyName ); + if( it != m_Properties.end() ) + return( it->second ); + + return( NULL ); + } + void FillEnumerateSet( IteratorState* it, CStrW* PropertyRoot = NULL ) + { + PropertyTable::iterator iit; + if( PropertyRoot ) + { + size_t rlen = PropertyRoot->Length(); + for( iit = m_Properties.begin(); iit != m_Properties.end(); iit++ ) + if( ( iit->first.Length() > rlen ) && ( iit->first.Left( rlen ) == *PropertyRoot ) && ( iit->first[rlen] == '.' ) ) + it->first.insert( iit->first.GetSubstring( rlen + 1, iit->first.Length() - rlen - 1 ).BeforeFirst( L"." ) ); + } + else + { + for( iit = m_Properties.begin(); iit != m_Properties.end(); iit++ ) + it->first.insert( iit->first.BeforeFirst( L"." ) ); + } + if( m_Parent ) + m_Parent->FillEnumerateSet( it, PropertyRoot ); + } + void AddProperty( CStrW PropertyName, jsval Value ) + { + assert( !HasProperty( PropertyName ) ); + CJSDynamicComplexProperty* newProp = new CJSValComplexProperty( Value, false ); + m_Properties[PropertyName] = newProp; + + ReflectorTable::iterator it; + it = m_Reflectors.find( PropertyName ); + if( it != m_Reflectors.end() ) + { + // We had an accessor pointing to this property before it was defined. + newProp->m_JSAccessor = it->second->m_JSAccessor; + JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it->second->m_JSAccessor ) ); + JS_AddRoot( g_ScriptingHost.GetContext(), &( newProp->m_JSAccessor ) ); + delete( it->second ); + m_Reflectors.erase( it ); + } + } + + void AddProperty( CStrW PropertyName, CStrW Value ) + { + AddProperty( PropertyName, JSParseString( Value ) ); + } + static void AddClassProperty( CStrW PropertyName, GetFn Getter, SetFn Setter = NULL ) + { + T::m_IntrinsicProperties[PropertyName] = new CJSFunctionComplexProperty( Getter, Setter ); + } + template + static void AddMethod( const char* Name, uintN MinArgs ) + { + JSFunctionSpec FnInfo = { Name, CNativeComplexFunction::JSFunction, MinArgs, 0, 0 }; + T::m_Methods.push_back( FnInfo ); + } + template static void AddClassProperty( CStrW PropertyName, PropType T::*Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) + { + T::m_IntrinsicProperties[PropertyName] = new CJSSharedProperty( (PropType IJSComplex::*)Native, PropAllowInheritance, Update, Refresh ); + } + template static void AddReadOnlyClassProperty( CStrW PropertyName, PropType T::*Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) + { + T::m_IntrinsicProperties[PropertyName] = new CJSSharedProperty( (PropType IJSComplex::*)Native, PropAllowInheritance, Update, Refresh ); + } + template void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) + { + m_Properties[PropertyName] = new CJSComplexProperty( Native, PropAllowInheritance, Update, Refresh ); + } + template void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) + { + m_Properties[PropertyName] = new CJSComplexProperty( Native, PropAllowInheritance, Update, Refresh ); + } +}; + +template JSClass CJSComplex::JSI_class = { + NULL, JSCLASS_HAS_PRIVATE | JSCLASS_NEW_ENUMERATE, + JS_PropertyStub, JS_PropertyStub, + JSGetProperty, JSSetProperty, + (JSEnumerateOp)JSEnumerate, JS_ResolveStub, + JS_ConvertStub, DefaultFinalize, + NULL, NULL, NULL, NULL +}; + +template JSPropertySpec CJSComplex::JSI_props[] = { + { 0 }, +}; + +template std::vector CJSComplex::m_Methods; +template typename CJSComplex::PropertyTable CJSComplex::m_IntrinsicProperties; + +template bool CJSComplex::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) +{ + IJSComplexProperty* Property = HasProperty( PropertyName ); + if( Property && Property->m_Intrinsic ) + { + *vp = Property->Get( cx, this ); + } + else + { + CJSValComplexProperty* extProp; + + if( Property ) + { + extProp = (CJSValComplexProperty*)Property; + + // If it's already a JS object, there's no point in creating + // a PropertyAccessor for it; it can manage far better on its + // own (this was why valueOf() was necessary) + + if( !JSVAL_IS_OBJECT( extProp->m_Data ) ) + { + if( !extProp->m_JSAccessor ) + { + extProp->m_JSAccessor = CJSComplexPropertyAccessor< CJSComplex >::CreateAccessor( cx, this, PropertyName ); + JS_AddNamedRoot( cx, &extProp->m_JSAccessor, "property accessor" ); + } + + *vp = OBJECT_TO_JSVAL( extProp->m_JSAccessor ); + } + else + *vp = extProp->m_Data; + } + else + { + // Check to see if it exists on a parent + IJSComplex* check = m_Parent; + while( check ) + { + if( check->HasProperty( PropertyName ) ) break; + check = check->m_Parent; + } + + if( !check ) + return( false ); + + // FIXME: Fiddle a way so this /doesn't/ require multiple kilobytes + // of memory. Can't think of any better way to do it yet. Problem is + // that script may access a property that isn't defined locally, but + // is defined by an ancestor. We can't return an accessor to the + // ancestor's property, because then if it's altered it affects that + // object, not this. At the moment, creating a 'reflector' property + // accessor that references /this/ object to be returned to script. + + // (N.B. Can't just put JSComplexs* in the table -> table entries can + // move -> root no longer refers to the JSObject.) + + ReflectorTable::iterator it; + it = m_Reflectors.find( PropertyName ); + + if( it == m_Reflectors.end() ) + { + CJSReflector* reflector = new CJSReflector(); + reflector->m_JSAccessor = CJSComplexPropertyAccessor< CJSComplex >::CreateAccessor( cx, this, PropertyName ); + JS_AddRoot( cx, &reflector->m_JSAccessor ); + m_Reflectors.insert( std::pair( PropertyName, reflector ) ); + *vp = OBJECT_TO_JSVAL( reflector->m_JSAccessor ); + } + else + *vp = OBJECT_TO_JSVAL( it->second->m_JSAccessor ); + } + } + return( true ); +} + +#endif + +