Browse Source

Updates to components model

Josh Engebretson 10 years ago
parent
commit
642f3ca185

+ 31 - 0
Script/AtomicEditor/ui/inspector/ComponentInspector.ts

@@ -111,6 +111,11 @@ class ComponentInspector extends Atomic.UISection {
           this.addLightCascadeParametersUI(attrsVerticalLayout);
         }
 
+        if (component.getTypeName() == "JSComponent") {
+          this.addJSComponentUI(attrsVerticalLayout);
+        }
+
+
         var deleteButton = new Atomic.UIButton();
         deleteButton.text = "Delete Component";
         deleteButton.fontDescription = fd;
@@ -136,6 +141,8 @@ class ComponentInspector extends Atomic.UISection {
 
     }
 
+    // Move these to a mixing class
+
     addPrefabUI(layout:Atomic.UILayout) {
 
       // expand prefab
@@ -161,6 +168,30 @@ class ComponentInspector extends Atomic.UISection {
 
       layout.addChild(selectButton);
 
+    }
+
+    addJSComponentUI(layout:Atomic.UILayout) {
+
+      // expand prefab
+      this.value = 1;
+
+      var fd = new Atomic.UIFontDescription();
+      fd.id = "Vera";
+      fd.size = 11;
+
+      var selectButton = new Atomic.UIButton();
+      selectButton.text = "Select Script";
+      selectButton.fontDescription = fd;
+
+      selectButton.onClick = () => {
+
+          return true;
+      }
+
+      var field = InspectorUtils.createAttrEditField("Script", layout);
+      field.readOnly = true;
+
+      layout.addChild(selectButton);
 
     }
 

+ 17 - 32
Script/TypeScript/Atomic.d.ts

@@ -746,24 +746,6 @@ declare module Atomic {
    export var FILE_READWRITE: FileMode;
 
 
-   // enum JSScriptMethod
-   export type JSScriptMethod = number;
-   export var JSMETHOD_START: JSScriptMethod;
-   export var JSMETHOD_STOP: JSScriptMethod;
-   export var JSMETHOD_DELAYEDSTART: JSScriptMethod;
-   export var JSMETHOD_UPDATE: JSScriptMethod;
-   export var JSMETHOD_POSTUPDATE: JSScriptMethod;
-   export var JSMETHOD_FIXEDUPDATE: JSScriptMethod;
-   export var JSMETHOD_FIXEDPOSTUPDATE: JSScriptMethod;
-   export var JSMETHOD_LOAD: JSScriptMethod;
-   export var JSMETHOD_SAVE: JSScriptMethod;
-   export var JSMETHOD_READNETWORKUPDATE: JSScriptMethod;
-   export var JSMETHOD_WRITENETWORKUPDATE: JSScriptMethod;
-   export var JSMETHOD_APPLYATTRIBUTES: JSScriptMethod;
-   export var JSMETHOD_TRANSFORMCHANGED: JSScriptMethod;
-   export var MAX_JSSCRIPT_METHODS: JSScriptMethod;
-
-
    export var QUICKSORT_THRESHOLD: number;
    export var CONVERSION_BUFFER_LENGTH: number;
    export var MATRIX_CONVERSION_BUFFER_LENGTH: number;
@@ -1418,6 +1400,8 @@ declare module Atomic {
    export class Context extends RefCounted {
 
       eventSender: AObject;
+      editorContext: boolean;
+      editorContent: boolean;
 
       // Construct.
       constructor();
@@ -1436,6 +1420,10 @@ declare module Atomic {
       getEventSender(): AObject;
       // Return object type name from hash, or empty if unknown.
       getTypeName(objectType: string): string;
+      // Get whether an Editor Context
+      getEditorContext(): boolean;
+      // Get whether an Editor Context
+      setEditorContent(editor: boolean): void;
 
    }
 
@@ -7029,11 +7017,13 @@ declare module Atomic {
    export class UIEditField extends UIWidget {
 
       textAlign: TEXT_ALIGN;
+      readOnly: boolean;
       wrapping: boolean;
 
       constructor(createWidget?: boolean);
 
       setTextAlign(align: TEXT_ALIGN): void;
+      setReadOnly(readonly: boolean): void;
       setWrapping(wrap: boolean): void;
       getWrapping(): boolean;
 
@@ -8024,25 +8014,20 @@ declare module Atomic {
 
    export class JSComponent extends Component {
 
-      className: string;
-      classNameProperty: string;
-      destroyed: boolean;
+      updateEventMask: number;
 
       // Construct.
       constructor();
 
-      // Return class name.
-      getClassName(): string;
-      setClassName(className: string): void;
-      getClassNameProperty(): string;
-      setClassNameProperty(className: string): void;
-      // Handle enabled/disabled state change.
-      onSetEnabled(): void;
-      onNodeSet(node: Node): void;
-      getDestroyed(): boolean;
-      setDestroyed(): void;
-      listenToEvent(sender: AObject, eventType: string, __duk_function: number): void;
       applyAttributes(): void;
+      // Handle enabled/disabled state change. Changes update event subscription.
+      onSetEnabled(): void;
+      // Set what update events should be subscribed to. Use this for optimization: by default all are in use. Note that this is not an attribute and is not saved or network-serialized, therefore it should always be called eg. in the subclass constructor.
+      setUpdateEventMask(mask: number): void;
+      // Return what update events are subscribed to.
+      getUpdateEventMask(): number;
+      // Return whether the DelayedStart() function has been called.
+      isDelayedStartCalled(): boolean;
 
    }
 

+ 2 - 1
Source/Atomic/Core/Context.cpp

@@ -52,7 +52,8 @@ void RemoveNamedAttribute(HashMap<StringHash, Vector<AttributeInfo> >& attribute
 }
 
 Context::Context() :
-    eventHandler_(0)
+    eventHandler_(0),
+    editorContext_(false)
 {
     #ifdef ANDROID
     // Always reset the random seed on Android, as the Atomic library might not be unloaded between runs

+ 7 - 0
Source/Atomic/Core/Context.h

@@ -96,6 +96,8 @@ public:
     template <class T> T* GetSubsystem() const;
     /// Template version of returning a specific attribute description.
     template <class T> AttributeInfo* GetAttribute(const char* name);
+    /// Get whether an Editor Context
+    bool GetEditorContext() { return editorContext_; }
 
     /// Return attribute descriptions for an object type, or null if none defined.
     const Vector<AttributeInfo>* GetAttributes(StringHash type) const
@@ -134,6 +136,9 @@ public:
         return i != eventReceivers_.End() ? &i->second_ : 0;
     }
 
+    /// Get whether an Editor Context
+    void SetEditorContent(bool editor) { editorContext_ = editor; }
+
 private:
     /// Add event receiver.
     void AddEventReceiver(Object* receiver, StringHash eventType);
@@ -172,6 +177,8 @@ private:
     EventHandler* eventHandler_;
     /// Object categories.
     HashMap<String, Vector<StringHash> > objectCategories_;
+
+    bool editorContext_;
 };
 
 template <class T> void Context::RegisterFactory() { RegisterFactory(new ObjectFactoryImpl<T>(this)); }

+ 11 - 1
Source/Atomic/UI/UIEditField.cpp

@@ -27,6 +27,17 @@ UIEditField::~UIEditField()
 
 }
 
+void UIEditField::SetReadOnly(bool readonly)
+{
+    if (!widget_)
+        return;
+
+    TBEditField* w = (TBEditField*) widget_;
+
+    w->SetReadOnly(readonly);
+
+}
+
 void UIEditField::SetWrapping(bool wrap)
 {
     if (!widget_)
@@ -35,7 +46,6 @@ void UIEditField::SetWrapping(bool wrap)
     TBEditField* w = (TBEditField*) widget_;
 
     w->SetWrapping(wrap);
-
 }
 
 bool UIEditField::GetWrapping()

+ 2 - 0
Source/Atomic/UI/UIEditField.h

@@ -25,6 +25,8 @@ public:
 
     void SetTextAlign(TEXT_ALIGN align);
 
+    void SetReadOnly(bool readonly);
+
     void SetWrapping(bool wrap);
     bool GetWrapping();
 

+ 2 - 0
Source/AtomicEditorWork/Application/AEEditorApp.cpp

@@ -47,6 +47,8 @@ void AEEditorApp::Start()
     // this can be toggled temporarily, for example to setup an animation preview
     AnimatedModel::SetBoneCreationEnabled(false);
 
+    context_->SetEditorContent(true);
+
     Input* input = GetSubsystem<Input>();
     input->SetMouseVisible(true);
 

+ 3 - 2
Source/AtomicJS/Javascript/JSAtomic.cpp

@@ -201,8 +201,9 @@ static void js_atomic_destroy_node(Node* node, duk_context* ctx, bool root = fal
 
          if (component->GetType() == JSComponent::GetTypeStatic())
          {
-             JSComponent* jscomponent = (JSComponent*) component;
-             jscomponent->SetDestroyed();
+             // FIX COMPONENTS
+             //JSComponent* jscomponent = (JSComponent*) component;
+             //jscomponent->SetDestroyed();
          }
 
          component->UnsubscribeFromAllEvents();

+ 266 - 383
Source/AtomicJS/Javascript/JSComponent.cpp

@@ -1,115 +1,71 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
 // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
 // Please see LICENSE.md in repository root for license information
 // https://github.com/AtomicGameEngine/AtomicGameEngine
 
-
-#include <Atomic/Core/Context.h>
 #include <Atomic/IO/Log.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/Core/Context.h>
+#include <Atomic/Resource/ResourceCache.h>
+
 #ifdef ATOMIC_PHYSICS
 #include <Atomic/Physics/PhysicsEvents.h>
 #include <Atomic/Physics/PhysicsWorld.h>
 #endif
-
-#include <Atomic/Core/Profiler.h>
-#include <Atomic/IO/MemoryBuffer.h>
-#include <Atomic/Resource/ResourceCache.h>
-#include <Atomic/Resource/ResourceEvents.h>
 #include <Atomic/Scene/Scene.h>
 #include <Atomic/Scene/SceneEvents.h>
-#include <Atomic/Atomic2D/PhysicsEvents2D.h>
-#include <Atomic/Atomic2D/PhysicsWorld2D.h>
-#include <Atomic/Atomic2D/RigidBody2D.h>
-#include <Atomic/UI/UIEvents.h>
 
-#include "Javascript.h"
-#include "JSEvents.h"
+#include "JSVM.h"
+#include "JSComponentFile.h"
 #include "JSComponent.h"
-#include "JSAPI.h"
 
 namespace Atomic
 {
 
-static const char* methodDeclarations[] = {
-    "start",
-    "stop",
-    "delayedStart",
-    "update",
-    "postUpdate",
-    "fixedUpdate",
-    "fixedPostUpdate",
-    "load",
-    "save",
-    "readNetworkUpdate",
-    "writeNetworkUpdate",
-    "applyAttributes",
-    "transformChanged"
-};
-
-
 extern const char* LOGIC_CATEGORY;
 
 JSComponent::JSComponent(Context* context) :
     Component(context),
-    script_(GetSubsystem<Javascript>()),
-    scriptObject_(0),
-    subscribed_(false),
-    subscribedPostFixed_(false),
+    updateEventMask_(USE_UPDATE | USE_POSTUPDATE | USE_FIXEDUPDATE | USE_FIXEDPOSTUPDATE),
+    currentEventMask_(0),
     started_(false),
-    destroyed_(false)
+    delayedStartCalled_(false),
+    loading_(false)
 {
     vm_ = JSVM::GetJSVM(NULL);
-    ClearScriptMethods();
 }
 
 JSComponent::~JSComponent()
 {
-}
-
-void JSComponent::OnNodeSet(Node *node)
-{
-    Component::OnNodeSet(node);
 
-    if (node && node->JSGetHeapPtr())
-    {
-        //assert(node->JSGetHeapPtr());
-
-        duk_context* ctx = vm_->GetJSContext();
-        int top = duk_get_top(ctx);
-        duk_push_global_stash(ctx);
-        duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_INDEX_NODE_REGISTRY);
-        // can't use instance as key, as this coerces to [Object] for
-        // string property, pointer will be string representation of
-        // address, so, unique key
-        duk_push_pointer(ctx, (void*) node);
-        js_push_class_object_instance(ctx, node);
-        duk_put_prop(ctx, -3);
-        duk_pop_2(ctx);
-        assert(duk_get_top(ctx) == top);
-    }
 }
 
 void JSComponent::RegisterObject(Context* context)
 {
     context->RegisterFactory<JSComponent>(LOGIC_CATEGORY);
     ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
-    ACCESSOR_ATTRIBUTE("Component Name", GetClassNameProperty, SetClassNameProperty, String, String::EMPTY, AM_DEFAULT);
-}
-
-void JSComponent::ApplyAttributes()
-{
-    Component::ApplyAttributes();
-
-    if (classNameProperty_.Length())
-        SetClassName(classNameProperty_);
-
-}
-
-void JSComponent::ClearScriptMethods()
-{
-    for (unsigned i = 0; i < MAX_JSSCRIPT_METHODS; ++i)
-        methods_[i] = 0;
-
-    //delayedCalls_.Clear();
+    MIXED_ACCESSOR_ATTRIBUTE("ComponentFile", GetScriptAttr, SetScriptAttr, ResourceRef, ResourceRef(JSComponentFile::GetTypeStatic()), AM_DEFAULT);
 }
 
 void JSComponent::OnSetEnabled()
@@ -117,423 +73,350 @@ void JSComponent::OnSetEnabled()
     UpdateEventSubscription();
 }
 
-void JSComponent::ListenToEvent(Object* sender, StringHash eventType, JS_HEAP_PTR __duk_function)
+void JSComponent::SetUpdateEventMask(unsigned char mask)
 {
-    duk_context* ctx = vm_->GetJSContext();
-    duk_push_heapptr(ctx, __duk_function);
-    assert(duk_is_function(ctx, -1));
-    duk_pop(ctx);
-
-    scriptEventFunctions_[eventType] = __duk_function;
-    if (sender)
-        SubscribeToEvent(sender, eventType, HANDLER(JSComponent, HandleScriptEvent));
-    else
-        SubscribeToEvent(eventType, HANDLER(JSComponent, HandleScriptEvent));
-
-}
-
-bool JSComponent::CreateObject(JSFile* scriptFile, const String& className)
-{
-    className_ = String::EMPTY; // Do not create object during SetScriptFile()
-    SetScriptFile(scriptFile);
-    SetClassName(className);
-    return scriptObject_ != 0;
+    if (updateEventMask_ != mask)
+    {
+        updateEventMask_ = mask;
+        UpdateEventSubscription();
+    }
 }
 
-void JSComponent::SetClassName(const String& className)
+void JSComponent::UpdateReferences(bool remove)
 {
-    assert(className.Length());
-
-    if (className == className_ && scriptObject_)
-        return;
+    duk_context* ctx = vm_->GetJSContext();
 
-    ReleaseObject();
+    int top = duk_get_top(ctx);
 
-    className_ = className;
-    CreateObject();
-    MarkNetworkUpdate();
-}
+    duk_push_global_stash(ctx);
+    duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_INDEX_NODE_REGISTRY);
 
+    // can't use instance as key, as this coerces to [Object] for
+    // string property, pointer will be string representation of
+    // address, so, unique key
 
-void JSComponent::ReleaseObject()
-{
-    if (scriptObject_)
-    {
-        //if (methods_[JSMETHOD_STOP])
-        //    scriptFile_->Execute(scriptObject_, methods_[JSMETHOD_STOP]);
+    duk_push_pointer(ctx, (void*) node_);
+    if (remove)
+        duk_push_undefined(ctx);
+    else
+        js_push_class_object_instance(ctx, node_);
 
-        PODVector<StringHash> exceptions;
-        exceptions.Push(E_RELOADSTARTED);
-        exceptions.Push(E_RELOADFINISHED);
-        UnsubscribeFromAllEventsExcept(exceptions, false);
+    duk_put_prop(ctx, -3);
 
-        if (node_)
-            node_->RemoveListener(this);
+    duk_push_pointer(ctx, (void*) this);
+    if (remove)
+        duk_push_undefined(ctx);
+    else
+        js_push_class_object_instance(ctx, this);
 
-        subscribed_ = false;
-        subscribedPostFixed_ = false;
+    duk_put_prop(ctx, -3);
 
-        ClearScriptMethods();
+    duk_pop_2(ctx);
 
-        scriptObject_ = 0;
-    }
+    assert(duk_get_top(ctx) == top);
 }
 
-
-void JSComponent::SetScriptFile(JSFile* scriptFile)
+void JSComponent::ApplyAttributes()
 {
 
-    ReleaseObject();
-
-    CreateObject();
-    MarkNetworkUpdate();
 }
 
-void JSComponent::CreateObject()
+void JSComponent::InitModule()
 {
-    if (className_.Empty())
+    if (context_->GetEditorContext() || componentFile_.Null())
         return;
 
-    PROFILE(CreateScriptObject);
-
     duk_context* ctx = vm_->GetJSContext();
 
-    duk_push_global_stash(ctx);
-    duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
-    duk_get_prop_string(ctx, -1, className_.CString());
+    const String& path = componentFile_->GetName();
+    String pathName, fileName, ext;
+    SplitPath(path, pathName, fileName, ext);
 
-    // FIXME: This is happening in editor
-    if (!duk_is_function(ctx, -1))
-    {
-        duk_pop_n(ctx, 3);
-        return;
-    }
+    pathName += "/" + fileName;
+
+    duk_idx_t top = duk_get_top(ctx);
 
-    js_push_class_object_instance(ctx, this);
+    duk_get_global_string(ctx, "require");
+    duk_push_string(ctx, pathName.CString());
 
     if (duk_pcall(ctx, 1) != 0)
     {
         vm_->SendJSErrorEvent();
+        return;
     }
-    else
+
+    if (!duk_is_object(ctx, -1))
     {
-        scriptObject_ = this->JSGetHeapPtr();
+        duk_set_top(ctx, top);
+        return;
     }
 
-    if (scriptObject_)
+    duk_get_prop_string(ctx, -1, "component");
+    if (!duk_is_function(ctx, -1))
     {
-        GetScriptMethods();
-        UpdateEventSubscription();
+        duk_set_top(ctx, top);
+        return;
     }
 
+    // call with self
+    js_push_class_object_instance(ctx, this, "JSComponent");
+
+    if (duk_pcall(ctx, 1) != 0)
+    {
+        vm_->SendJSErrorEvent();
+        duk_set_top(ctx, top);
+        return;
+    }
+
+    UpdateReferences();
+
+    duk_set_top(ctx, top);
 
-    duk_pop_n(ctx, 2);
 }
 
-void JSComponent::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
+void JSComponent::CallScriptMethod(const String& name, bool passValue, float value)
 {
-    if (!scriptObject_)
-        return;
+    void* heapptr = JSGetHeapPtr();
 
-    assert(!destroyed_);
+    if (!heapptr)
+        return;
 
-    assert(JSGetHeapPtr());
+    duk_context* ctx = vm_->GetJSContext();
 
-    using namespace SceneUpdate;
+    duk_idx_t top = duk_get_top(ctx);
 
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    duk_push_heapptr(ctx, heapptr);
 
-    duk_context* ctx = vm_->GetJSContext();
+    duk_get_prop_string(ctx, -1, name.CString());
 
-    if (!started_)
+    if (!duk_is_function(ctx, -1))
     {
-        started_ = true;
+        duk_set_top(ctx, top);
+        return;
+    }
 
-        if (methods_[JSMETHOD_START])
-        {
-            duk_push_heapptr(ctx, methods_[JSMETHOD_START]);
-            if (duk_pcall(ctx, 0) != 0)
-            {
-                vm_->SendJSErrorEvent();
-            }
+    if (passValue)
+        duk_push_number(ctx, value);
 
-            duk_pop(ctx);
-        }
+    if (duk_pcall(ctx, passValue ? 1 : 0) != 0)
+    {
+        vm_->SendJSErrorEvent();
+        duk_set_top(ctx, top);
+        return;
     }
 
-    if (methods_[JSMETHOD_UPDATE])
-    {        
-        duk_push_heapptr(ctx, methods_[JSMETHOD_UPDATE]);
-        duk_push_number(ctx, timeStep);
+    duk_set_top(ctx, top);
+}
 
-        if ( duk_pcall(ctx, 1) != DUK_EXEC_SUCCESS)
-        {
-            if (duk_is_object(ctx, -1))
-            {
-                vm_->SendJSErrorEvent();
-            }
-            else
-            {
-                assert(0);
-            }
-        }
+void JSComponent::Start()
+{
+    static String name = "start";
+    CallScriptMethod(name);
+}
+
+void JSComponent::DelayedStart()
+{
+    static String name = "delayedStart";
+    CallScriptMethod(name);
+}
+
+void JSComponent::Update(float timeStep)
+{
+    static String name = "update";
+    CallScriptMethod(name, true, timeStep);
+}
+
+void JSComponent::PostUpdate(float timeStep)
+{
+    static String name = "postUpdate";
+    CallScriptMethod(name, true, timeStep);
+}
+
+void JSComponent::FixedUpdate(float timeStep)
+{
+    static String name = "fixedUpdate";
+    CallScriptMethod(name, true, timeStep);
+}
 
-        duk_pop(ctx);
+void JSComponent::FixedPostUpdate(float timeStep)
+{
+    static String name = "fixedPostUpdate";
+    CallScriptMethod(name, true, timeStep);
+}
+
+void JSComponent::OnNodeSet(Node* node)
+{
+    if (node)
+    {
+        // We have been attached to a node. Set initial update event subscription state
+        UpdateEventSubscription();
     }
     else
     {
-        Scene* scene = GetScene();
-        if (scene)
-            UnsubscribeFromEvent(scene, E_SCENEUPDATE);
-        subscribed_ = false;
+        // We are being detached from a node: execute user-defined stop function and prepare for destruction
+        UpdateReferences(true);
+        Stop();
     }
 }
 
 void JSComponent::UpdateEventSubscription()
 {
+    // If scene node is not assigned yet, no need to update subscription
+    if (!node_)
+        return;
+
     Scene* scene = GetScene();
     if (!scene)
     {
-        LOGWARNING("Node is detached from scene, can not subscribe script object to update events");
+        LOGWARNING("Node is detached from scene, can not subscribe to update events");
         return;
     }
 
-    bool enabled = scriptObject_ && IsEnabledEffective();
+    bool enabled = IsEnabledEffective();
 
-    if (enabled)
+    bool needUpdate = enabled && ((updateEventMask_ & USE_UPDATE) || !delayedStartCalled_);
+    if (needUpdate && !(currentEventMask_ & USE_UPDATE))
     {
-        // we get at least one scene update if not started
-        if (!subscribed_ && (!started_ || (methods_[JSMETHOD_UPDATE] || methods_[JSMETHOD_DELAYEDSTART] )))
-        {
-            SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(JSComponent, HandleSceneUpdate));
-            subscribed_ = true;
-        }
+        SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(JSComponent, HandleSceneUpdate));
+        currentEventMask_ |= USE_UPDATE;
+    }
+    else if (!needUpdate && (currentEventMask_ & USE_UPDATE))
+    {
+        UnsubscribeFromEvent(scene, E_SCENEUPDATE);
+        currentEventMask_ &= ~USE_UPDATE;
+    }
 
-        if (!subscribedPostFixed_)
-        {
-            if (methods_[JSMETHOD_POSTUPDATE])
-                SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(JSComponent, HandleScenePostUpdate));
+    bool needPostUpdate = enabled && (updateEventMask_ & USE_POSTUPDATE);
+    if (needPostUpdate && !(currentEventMask_ & USE_POSTUPDATE))
+    {
+        SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(JSComponent, HandleScenePostUpdate));
+        currentEventMask_ |= USE_POSTUPDATE;
+    }
+    else if (!needUpdate && (currentEventMask_ & USE_POSTUPDATE))
+    {
+        UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
+        currentEventMask_ &= ~USE_POSTUPDATE;
+    }
 
 #ifdef ATOMIC_PHYSICS
-            if (methods_[JSMETHOD_FIXEDUPDATE] || methods_[JSMETHOD_FIXEDPOSTUPDATE])
-            {
-                PhysicsWorld* world = scene->GetOrCreateComponent<PhysicsWorld>();
-                if (world)
-                {
-                    if (methods_[JSMETHOD_FIXEDUPDATE])
-                        SubscribeToEvent(world, E_PHYSICSPRESTEP, HANDLER(JSComponent, HandlePhysicsPreStep));
-                    if (methods_[JSMETHOD_FIXEDPOSTUPDATE])
-                        SubscribeToEvent(world, E_PHYSICSPOSTSTEP, HANDLER(JSComponent, HandlePhysicsPostStep));
-                }
-                else
-                    LOGERROR("No physics world, can not subscribe script object to fixed update events");
-            }
-#endif
-            subscribedPostFixed_ = true;
-        }
+    PhysicsWorld* world = scene->GetComponent<PhysicsWorld>();
+    if (!world)
+        return;
 
-        if (methods_[JSMETHOD_TRANSFORMCHANGED])
-            node_->AddListener(this);
+    bool needFixedUpdate = enabled && (updateEventMask_ & USE_FIXEDUPDATE);
+    if (needFixedUpdate && !(currentEventMask_ & USE_FIXEDUPDATE))
+    {
+        SubscribeToEvent(world, E_PHYSICSPRESTEP, HANDLER(JSComponent, HandlePhysicsPreStep));
+        currentEventMask_ |= USE_FIXEDUPDATE;
     }
-    else
+    else if (!needFixedUpdate && (currentEventMask_ & USE_FIXEDUPDATE))
     {
-        if (subscribed_)
-        {
-            UnsubscribeFromEvent(scene, E_SCENEUPDATE);
-            subscribed_ = false;
-        }
+        UnsubscribeFromEvent(world, E_PHYSICSPRESTEP);
+        currentEventMask_ &= ~USE_FIXEDUPDATE;
+    }
 
-        if (subscribedPostFixed_)
-        {
-            UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
-#ifdef ATOMIC_PHYSICS
-            PhysicsWorld* world = scene->GetComponent<PhysicsWorld>();
-            if (world)
-            {
-                UnsubscribeFromEvent(world, E_PHYSICSPRESTEP);
-                UnsubscribeFromEvent(world, E_PHYSICSPOSTSTEP);
-            }
+    bool needFixedPostUpdate = enabled && (updateEventMask_ & USE_FIXEDPOSTUPDATE);
+    if (needFixedPostUpdate && !(currentEventMask_ & USE_FIXEDPOSTUPDATE))
+    {
+        SubscribeToEvent(world, E_PHYSICSPOSTSTEP, HANDLER(JSComponent, HandlePhysicsPostStep));
+        currentEventMask_ |= USE_FIXEDPOSTUPDATE;
+    }
+    else if (!needFixedPostUpdate && (currentEventMask_ & USE_FIXEDPOSTUPDATE))
+    {
+        UnsubscribeFromEvent(world, E_PHYSICSPOSTSTEP);
+        currentEventMask_ &= ~USE_FIXEDPOSTUPDATE;
+    }
 #endif
+}
 
-            subscribedPostFixed_ = false;
-        }
+void JSComponent::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace SceneUpdate;
 
-        if (methods_[JSMETHOD_TRANSFORMCHANGED])
-            node_->RemoveListener(this);
+    if (!started_)
+    {
+        started_ = true;
+        InitModule();
+        Start();
     }
-}
 
+    // Execute user-defined delayed start function before first update
+    if (!delayedStartCalled_)
+    {
+        DelayedStart();
+        delayedStartCalled_ = true;
 
-void JSComponent::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
-{
-    if (!scriptObject_)
-        return;
+        // If did not need actual update events, unsubscribe now
+        if (!(updateEventMask_ & USE_UPDATE))
+        {
+            UnsubscribeFromEvent(GetScene(), E_SCENEUPDATE);
+            currentEventMask_ &= ~USE_UPDATE;
+            return;
+        }
+    }
 
-    assert(!destroyed_);
+    // Then execute user-defined update function
+    Update(eventData[P_TIMESTEP].GetFloat());
+}
 
+void JSComponent::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
+{
     using namespace ScenePostUpdate;
 
-    if (methods_[JSMETHOD_POSTUPDATE])
-    {
-        duk_context* ctx = vm_->GetJSContext();
-        duk_push_heapptr(ctx, methods_[JSMETHOD_POSTUPDATE]);
-        duk_push_number(ctx, eventData[P_TIMESTEP].GetFloat());
-        duk_pcall(ctx, 1);
-        duk_pop(ctx);
-    }
+    // Execute user-defined post-update function
+    PostUpdate(eventData[P_TIMESTEP].GetFloat());
 }
 
 #ifdef ATOMIC_PHYSICS
 void JSComponent::HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
 {
-    if (!scriptObject_)
-        return;
-
-    assert(!destroyed_);
-
     using namespace PhysicsPreStep;
 
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
-
-    if (methods_[JSMETHOD_FIXEDUPDATE])
-    {
-        duk_context* ctx = vm_->GetJSContext();
-        duk_push_heapptr(ctx, methods_[JSMETHOD_FIXEDUPDATE]);
-        duk_push_number(ctx, timeStep);
-        duk_pcall(ctx, 1);
-        duk_pop(ctx);
-    }
+    // Execute user-defined fixed update function
+    FixedUpdate(eventData[P_TIMESTEP].GetFloat());
 }
 
 void JSComponent::HandlePhysicsPostStep(StringHash eventType, VariantMap& eventData)
 {
-    if (!scriptObject_)
-        return;
-
-    assert(!destroyed_);
-
     using namespace PhysicsPostStep;
 
-    VariantVector parameters;
-    parameters.Push(eventData[P_TIMESTEP]);
+    // Execute user-defined fixed post-update function
+    FixedPostUpdate(eventData[P_TIMESTEP].GetFloat());
 }
 #endif
-void JSComponent::HandleScriptEvent(StringHash eventType, VariantMap& eventData)
-{
-    if (!IsEnabledEffective() || !scriptObject_)
-        return;
-
-    assert(!destroyed_);
-
-    if (scriptEventFunctions_.Contains(eventType))
-    {
-
-        duk_context* ctx = vm_->GetJSContext();
-        JS_HEAP_PTR function = scriptEventFunctions_[eventType];
-
-        if (eventType == E_PHYSICSBEGINCONTACT2D || E_PHYSICSENDCONTACT2D)
-        {
-            using namespace PhysicsBeginContact2D;
-            PhysicsWorld2D* world = static_cast<PhysicsWorld2D*>(eventData[P_WORLD].GetPtr());
-            RigidBody2D* bodyA = static_cast<RigidBody2D*>(eventData[P_BODYA].GetPtr());
-            RigidBody2D* bodyB = static_cast<RigidBody2D*>(eventData[P_BODYB].GetPtr());
-            Node* nodeA = static_cast<Node*>(eventData[P_NODEA].GetPtr());
-            Node* nodeB = static_cast<Node*>(eventData[P_NODEB].GetPtr());
-
-            duk_push_heapptr(ctx, function);
-            js_push_class_object_instance(ctx, world);
-            js_push_class_object_instance(ctx, bodyA);
-            js_push_class_object_instance(ctx, bodyB);
-            js_push_class_object_instance(ctx, nodeA);
-            js_push_class_object_instance(ctx, nodeB);
-
-            if (duk_pcall(ctx, 5) != 0)
-            {
-                vm_->SendJSErrorEvent();
-            }
-
-            duk_pop(ctx);
-
-        }
-#ifdef ATOMIC_PHYSICS
-        else if (eventType == E_NODECOLLISION)
-        {
-            // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
-            using namespace NodeCollision;
-            MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
-
-            while (!contacts.IsEof())
-            {
-                Vector3 contactPosition = contacts.ReadVector3();
-                Vector3 contactNormal = contacts.ReadVector3();
-                float contactDistance = contacts.ReadFloat();
-                float contactImpulse = contacts.ReadFloat();
-
-                duk_push_heapptr(ctx, function);
-
-                duk_push_array(ctx);
-                duk_push_number(ctx, contactPosition.x_);
-                duk_put_prop_index(ctx, -2, 0);
-                duk_push_number(ctx, contactPosition.y_);
-                duk_put_prop_index(ctx, -2, 1);
-                duk_push_number(ctx, contactPosition.z_);
-                duk_put_prop_index(ctx, -2, 2);
-
-                duk_push_array(ctx);
-                duk_push_number(ctx, contactNormal.x_);
-                duk_put_prop_index(ctx, -2, 0);
-                duk_push_number(ctx, contactNormal.y_);
-                duk_put_prop_index(ctx, -2, 1);
-                duk_push_number(ctx, contactNormal.z_);
-                duk_put_prop_index(ctx, -2, 2);
-
-                duk_call(ctx, 2);
-                duk_pop(ctx);
-
-            }
-
-        }
-#endif
-        else
-        {
-            duk_push_heapptr(ctx, function);
-            if (duk_pcall(ctx, 0) != 0)
-            {
-                vm_->SendJSErrorEvent();
-            }
 
-            duk_pop(ctx);
-
-        }
-    }
+bool JSComponent::Load(Deserializer& source, bool setInstanceDefault)
+{
+    loading_ = true;
+    bool success = Component::Load(source, setInstanceDefault);
+    loading_ = false;
 
+    return success;
 }
 
-
-void JSComponent::GetScriptMethods()
+bool JSComponent::LoadXML(const XMLElement& source, bool setInstanceDefault)
 {
-    if (!scriptObject_)
-        return;
-
-    duk_context* ctx = vm_->GetJSContext();
-    duk_push_heapptr(ctx, scriptObject_);
-
-    for (unsigned i = 0; i < MAX_JSSCRIPT_METHODS; ++i)
-    {
-        duk_get_prop_string(ctx, -1, methodDeclarations[i]);
-        if (duk_is_function(ctx, -1))
-        {
-            methods_[i] = duk_get_heapptr(ctx, -1);
-        }
-
-        duk_pop(ctx);
-    }
-
-    duk_pop(ctx);
+    loading_ = true;
+    bool success = Component::LoadXML(source, setInstanceDefault);
+    loading_ = false;
 
+    return success;
 }
 
+void JSComponent::SetComponentFile(JSComponentFile* cfile, bool loading)
+{
+    componentFile_ = cfile;
+}
 
+ResourceRef JSComponent::GetScriptAttr() const
+{
+    return GetResourceRef(componentFile_, JSComponentFile::GetTypeStatic());
+}
 
+void JSComponent::SetScriptAttr(const ResourceRef& value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetComponentFile(cache->GetResource<JSComponentFile>(value.name_), loading_);
+}
 
 }

+ 85 - 74
Source/AtomicJS/Javascript/JSComponent.h

@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
 // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
 // Please see LICENSE.md in repository root for license information
 // https://github.com/AtomicGameEngine/AtomicGameEngine
@@ -6,121 +28,110 @@
 
 #include <Atomic/Scene/Component.h>
 
+#include "JSComponentFile.h"
+
 namespace Atomic
 {
 
-class Javascript;
-class JSFile;
 class JSVM;
 
-/// Inbuilt scripted component methods.
-enum JSScriptMethod
-{
-    JSMETHOD_START = 0,
-    JSMETHOD_STOP,
-    JSMETHOD_DELAYEDSTART,
-    JSMETHOD_UPDATE,
-    JSMETHOD_POSTUPDATE,
-    JSMETHOD_FIXEDUPDATE,
-    JSMETHOD_FIXEDPOSTUPDATE,
-    JSMETHOD_LOAD,
-    JSMETHOD_SAVE,
-    JSMETHOD_READNETWORKUPDATE,
-    JSMETHOD_WRITENETWORKUPDATE,
-    JSMETHOD_APPLYATTRIBUTES,
-    JSMETHOD_TRANSFORMCHANGED,
-    MAX_JSSCRIPT_METHODS
-};
-
+/// Helper base class for user-defined game logic components that hooks up to update events and forwards them to virtual functions similar to ScriptInstance class.
 class ATOMIC_API JSComponent : public Component
 {
-    OBJECT(JSComponent);
+    enum EventFlags
+    {
+        USE_UPDATE = 0x1,
+        USE_POSTUPDATE = 0x2,
+        USE_FIXEDUPDATE = 0x4,
+        USE_FIXEDPOSTUPDATE = 0x8
+    };
 
 public:
+
+    OBJECT(JSComponent);
+
     /// Construct.
     JSComponent(Context* context);
     /// Destruct.
     virtual ~JSComponent();
+
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Return class name.
-    const String& GetClassName() const { return className_; }
-    /// Return script file attribute.
-    ResourceRef GetScriptFileAttr() const;
+    bool Load(Deserializer& source, bool setInstanceDefault);
+    bool LoadXML(const XMLElement& source, bool setInstanceDefault);
+    void ApplyAttributes();
 
-    void SetScriptFileAttr(ResourceRef value);
-    void SetClassName(const String& className);
+    /// Get script attribute
+    ResourceRef GetScriptAttr() const;
 
-    const String& GetClassNameProperty() const { return classNameProperty_; }
-    void SetClassNameProperty(const String& className) { classNameProperty_ = className; }
+    /// Set script attribute.
+    void SetScriptAttr(const ResourceRef& value);
 
-    /// Handle enabled/disabled state change.
+    /// Handle enabled/disabled state change. Changes update event subscription.
     virtual void OnSetEnabled();
-    void OnNodeSet(Node *node);
 
-    bool GetDestroyed() { return destroyed_; }
-    void SetDestroyed() { destroyed_ = true; }
+    /// Set what update events should be subscribed to. Use this for optimization: by default all are in use. Note that this is not an attribute and is not saved or network-serialized, therefore it should always be called eg. in the subclass constructor.
+    void SetUpdateEventMask(unsigned char mask);
 
-    /// Create object of certain class from the script file. Return true if successful.
-    bool CreateObject(JSFile* scriptFile, const String& className);
-    /// Set script file only. Recreate object if necessary.
-    void SetScriptFile(JSFile* scriptFile);
+    /// Return what update events are subscribed to.
+    unsigned char GetUpdateEventMask() const { return updateEventMask_; }
+    /// Return whether the DelayedStart() function has been called.
+    bool IsDelayedStartCalled() const { return delayedStartCalled_; }
 
-    void ListenToEvent(Object* sender, StringHash eventType, JS_HEAP_PTR __duk_function);
+    void SetComponentFile(JSComponentFile* cfile, bool loading = false);
 
-    void ApplyAttributes();
+protected:
+    /// Handle scene node being assigned at creation.
+    virtual void OnNodeSet(Node* node);
 
 private:
-
-    /// (Re)create the script object and check for supported methods if successfully created.
-    void CreateObject();
-    void ReleaseObject();
-
-    void ClearScriptMethods();
+    /// Subscribe/unsubscribe to update events based on current enabled state and update event mask.
     void UpdateEventSubscription();
-
-    void GetScriptMethods();
-
     /// Handle scene update event.
     void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle scene post-update event.
-    void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);
+    void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);       
 #ifdef ATOMIC_PHYSICS
     /// Handle physics pre-step event.
     void HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData);
     /// Handle physics post-step event.
     void HandlePhysicsPostStep(StringHash eventType, VariantMap& eventData);
 #endif
-    /// Handle an event in script.
-    void HandleScriptEvent(StringHash eventType, VariantMap& eventData);
 
-    /// Class name.
-    String className_;
+    void CallScriptMethod(const String& name, bool passValue = false, float value = 0.0f);
+
+    /// Called when the component is added to a scene node. Other components may not yet exist.
+    virtual void Start();
+    /// Called before the first update. At this point all other components of the node should exist. Will also be called if update events are not wanted; in that case the event is immediately unsubscribed afterward.
+    virtual void DelayedStart();
+    /// Called when the component is detached from a scene node, usually on destruction. Note that you will no longer have access to the node and scene at that point.
+    virtual void Stop() {}
+    /// Called on scene update, variable timestep.
+    virtual void Update(float timeStep);
+    /// Called on scene post-update, variable timestep.
+    virtual void PostUpdate(float timeStep);
+    /// Called on physics update, fixed timestep.
+    virtual void FixedUpdate(float timeStep);
+    /// Called on physics post-update, fixed timestep.
+    virtual void FixedPostUpdate(float timeStep);
+
+    void InitModule();
+    void UpdateReferences(bool remove = false);
+
+    /// Requested event subscription mask.
+    unsigned char updateEventMask_;
+    /// Current event subscription mask.
+    unsigned char currentEventMask_;
 
-    String classNameProperty_;
+    bool started_;
 
-    /// Script subsystem.
-    SharedPtr<Javascript> script_;
+    /// Flag for delayed start.
+    bool delayedStartCalled_;
 
+    bool loading_;
     WeakPtr<JSVM> vm_;
-
-    HashMap<StringHash, JS_HEAP_PTR> scriptEventFunctions_;
-
-    /// Script object.
-    void* scriptObject_;
-
-    /// Pointers to supported inbuilt methods.
-    void* methods_[MAX_JSSCRIPT_METHODS];
-
-    /// Subscribed to scene update events flag.
-    bool subscribed_;
-
-    /// Subscribed to scene post and fixed update events flag.
-    bool subscribedPostFixed_;
-
-    bool started_;
-    bool destroyed_;
+    SharedPtr<JSComponentFile> componentFile_;
 
 };
 

+ 64 - 0
Source/AtomicJS/Javascript/JSComponentFile.cpp

@@ -0,0 +1,64 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/Core/Context.h>
+#include <Atomic/IO/Deserializer.h>
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/Profiler.h>
+#include <Atomic/Resource/ResourceCache.h>
+#include <Atomic/IO/Serializer.h>
+
+#include "JSComponentFile.h"
+
+namespace Atomic
+{
+
+JSComponentFile::JSComponentFile(Context* context) :
+    Resource(context)
+{
+}
+
+JSComponentFile::~JSComponentFile()
+{
+
+}
+
+void JSComponentFile::RegisterObject(Context* context)
+{
+    context->RegisterFactory<JSComponentFile>();
+}
+
+bool JSComponentFile::BeginLoad(Deserializer& source)
+{
+    SetMemoryUse(0);
+
+    return true;
+}
+
+bool JSComponentFile::Save(Serializer& dest) const
+{
+    return true;
+}
+
+
+
+}

+ 53 - 0
Source/AtomicJS/Javascript/JSComponentFile.h

@@ -0,0 +1,53 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Resource/Resource.h>
+#include <Atomic/Container/ArrayPtr.h>
+
+namespace Atomic
+{
+
+/// Script document resource.
+class ATOMIC_API JSComponentFile : public Resource
+{
+    OBJECT(JSComponentFile);
+
+public:
+    /// Construct.
+    JSComponentFile(Context* context);
+    /// Destruct.
+    virtual ~JSComponentFile();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Load resource from stream. May be called from a worker thread. Return true if successful.
+    virtual bool BeginLoad(Deserializer& source);
+    /// Save resource
+    virtual bool Save(Serializer& dest) const;
+
+private:
+
+};
+
+}

+ 2 - 0
Source/AtomicJS/Javascript/JSMetrics.cpp

@@ -93,6 +93,7 @@ void JSMetrics::DumpJSComponents()
             classname = ((Object*) itr->second_)->GetTypeName();
         }
 
+        /*
         if (classname == "JSComponent")
         {
             JSComponent* jsc = (JSComponent*) itr->second_;
@@ -112,6 +113,7 @@ void JSMetrics::DumpJSComponents()
             else
                 jscomponents[classname]++;
         }
+        */
 
         itr++;
     }

+ 8 - 1
Source/AtomicJS/Javascript/JSScene.cpp

@@ -18,10 +18,17 @@ void jsapi_init_scene_serializable(JSVM* vm);
 
 static int Node_CreateJSComponent(duk_context* ctx)
 {
+    String path = duk_require_string(ctx, 0);
+
     duk_push_this(ctx);
     Node* node = js_to_class_instance<Node>(ctx, -1, 0);
     JSComponent* jsc = node->CreateComponent<JSComponent>();
-    jsc->SetClassName(duk_to_string(ctx, 0));
+
+    ResourceCache* cache = node->GetContext()->GetSubsystem<ResourceCache>();
+    JSComponentFile* file = cache->GetResource<JSComponentFile>(path);
+
+    jsc->SetComponentFile(file);
+
     js_push_class_object_instance(ctx, jsc, "JSComponent");
     return 1;
 }

+ 0 - 161
Source/AtomicJS/Javascript/JSVM.cpp

@@ -64,8 +64,6 @@ void JSVM::InitJSContext()
     js_init_jsplugin(this);
     jsapi_init_atomic(this);
 
-    InitComponents();
-
     ui_ = new JSUI(context_);
 
     // handle this elsewhere?
@@ -153,165 +151,6 @@ bool JSVM::ExecuteFunction(const String& functionName)
 
 }
 
-bool JSVM::GenerateComponent(const String &cname, const String &jsfilename, const String& csource)
-{
-
-    String source = "(function() {var start = null; var update = null; var fixedUpdate = null; var postUpdate = null;\n function __component_function(self) {\n";
-
-    source += csource.CString();
-
-    source += "self.node.components = self.node.components || {};\n";
-
-    source.AppendWithFormat("self.node.components[\"%s\"] = self.node.components[\"%s\"] || [];\n",
-                            cname.CString(), cname.CString());
-
-
-    source += "if (start instanceof Function) self.start = start; " \
-              "if (update instanceof Function) self.update = update; "\
-              "if (fixedUpdate instanceof Function) self.fixedUpdate = fixedUpdate; " \
-              "if (postUpdate instanceof Function) self.postUpdate = postUpdate;\n";
-
-    String scriptName = cname;
-    scriptName[0] = tolower(scriptName[0]);
-
-    source.AppendWithFormat("self.node.%s = self.node.%s || self;\n",
-                            scriptName.CString(), scriptName.CString());
-
-    source.AppendWithFormat("self.node.components[\"%s\"].push(self);\n",
-                            cname.CString());
-
-    source += "}\n return __component_function;\n});";
-
-    duk_push_string(ctx_, jsfilename.CString());
-
-    if (duk_eval_raw(ctx_, source.CString(), source.Length(),
-                     DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_SAFE) != 0)
-    {
-        if (duk_is_object(ctx_, -1))
-        {
-            SendJSErrorEvent(jsfilename);
-            duk_pop(ctx_);
-        }
-        else
-        {
-            assert(0);
-        }
-    }
-    else if (duk_pcall(ctx_, 0) != 0)
-    {
-        if (duk_is_object(ctx_, -1))
-        {
-            SendJSErrorEvent(jsfilename);
-            duk_pop(ctx_);
-        }
-        else
-        {
-            assert(0);
-        }
-    }
-    else
-    {
-        if (!duk_is_function(ctx_, -1))
-        {
-            const char* error = duk_to_string(ctx_, -1);
-            SendJSErrorEvent();
-        }
-
-        duk_put_prop_string(ctx_, -2, cname.CString());
-        return true;
-    }
-
-    return false;
-}
-
-void JSVM::InitPackageComponents()
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-
-    duk_push_global_stash(ctx_);
-    duk_get_prop_index(ctx_, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
-
-    const Vector<SharedPtr<PackageFile> >& packageFiles = cache->GetPackageFiles();
-
-    for (unsigned i = 0; i < packageFiles.Size(); i++)
-    {
-        SharedPtr<PackageFile> package = packageFiles[i];
-        const Vector<String>& files =  package->GetCaseEntryNames();
-
-        for (unsigned j = 0; j < files.Size(); j++)
-        {
-            String name = files[j];
-            if (!name.StartsWith("Components/"))
-                continue;
-
-            String cname = GetFileName(name);
-            String jsname = name;
-
-            SharedPtr<File> jsfile(cache->GetFile(name));
-            String csource;
-            jsfile->ReadText(csource);
-
-            if (!GenerateComponent(cname, jsname, csource))
-                break;
-
-        }
-    }
-
-    // pop stash and component object
-    duk_pop_2(ctx_);
-
-}
-
-void JSVM::InitComponents()
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-
-    // TODO: better way to detect player?
-    const Vector<SharedPtr<PackageFile> >& packageFiles = cache->GetPackageFiles();
-    for (unsigned i = 0; i < packageFiles.Size(); i++)
-    {
-        String packageName = packageFiles[i]->GetName();
-        if (packageName.Find("AtomicResources") != String::NPOS)
-        {
-            InitPackageComponents();
-            return;
-        }
-    }
-
-    FileSystem* fileSystem = GetSubsystem<FileSystem>();
-    const Vector<String>& dirs = cache->GetResourceDirs();
-
-    duk_push_global_stash(ctx_);
-    duk_get_prop_index(ctx_, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
-
-    for (unsigned i = 0; i < dirs.Size(); i++)
-    {
-        Vector<String> files;
-
-        fileSystem->ScanDir(files ,dirs[i]+"/Components", "*.js", SCAN_FILES, true );
-
-        for (unsigned j = 0; j < files.Size(); j++)
-        {
-            String cname = GetFileName(files[j]);
-            String jsname = dirs[i]+"Components/" + files[j];
-
-            SharedPtr<File> jsfile = cache->GetFile("Components/" + files[j]);
-
-            String csource;
-            jsfile->ReadText(csource);
-
-            if (!GenerateComponent(cname, jsname, csource))
-                break;
-
-        }
-
-    }
-
-    // pop stash and component object
-    duk_pop_2(ctx_);
-
-}
-
 void JSVM::SendJSErrorEvent(const String& filename)
 {
     duk_context* ctx = GetJSContext();

+ 0 - 5
Source/AtomicJS/Javascript/JSVM.h

@@ -165,11 +165,6 @@ public:
 
 private:
 
-    bool GenerateComponent(const String& cname, const String& jsfilename, const String& csource);
-
-    void InitComponents();
-    void InitPackageComponents();
-
     void SubscribeToEvents();
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     

+ 2 - 0
Source/AtomicJS/Javascript/Javascript.cpp

@@ -13,6 +13,7 @@
 
 #include "Javascript.h"
 #include "JSComponent.h"
+#include "JSComponentFile.h"
 #include "JSVM.h"
 
 namespace Atomic
@@ -47,6 +48,7 @@ void Javascript::ShutdownVM(const String& name)
 
 void RegisterJavascriptLibrary(Context* context)
 {
+    JSComponentFile::RegisterObject(context);
     JSComponent::RegisterObject(context);
 }
 

+ 24 - 3
Source/ToolCore/Assets/JavascriptImporter.cpp

@@ -1,4 +1,6 @@
 
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/File.h>
 #include <Atomic/Resource/ResourceCache.h>
 #include <Atomic/Resource/Image.h>
 
@@ -12,6 +14,7 @@ namespace ToolCore
 JavascriptImporter::JavascriptImporter(Context* context, Asset *asset) : AssetImporter(context, asset)
 {
     requiresCacheFile_ = false;
+    isComponentFile_ = false;
 }
 
 JavascriptImporter::~JavascriptImporter()
@@ -26,12 +29,26 @@ void JavascriptImporter::SetDefaults()
 
 bool JavascriptImporter::Import(const String& guid)
 {
-    AssetDatabase* db = GetSubsystem<AssetDatabase>();
-    Asset* asset = db->GetAssetByGUID(guid);
+    isComponentFile_ = false;
 
-    if (!asset)
+    const String& path = asset_->GetPath();
+
+    SharedPtr<File> file(new File(context_, path, FILE_READ));
+
+    unsigned dataSize = file->GetSize();
+
+    SharedArrayPtr<char> buffer(new char[dataSize + 1]);
+
+    if (file->Read(buffer.Get(), dataSize) != dataSize)
         return false;
 
+    buffer[dataSize] = '\0';
+
+    file->Close();
+
+    if (strstr(buffer, "\"atomic component\";"))
+        isComponentFile_ = true;
+
     return true;
 }
 
@@ -42,6 +59,8 @@ bool JavascriptImporter::LoadSettingsInternal()
 
     JSONValue import = jsonRoot_.GetChild("JavascriptImporter", JSON_OBJECT);
 
+    isComponentFile_ = import.GetBool("IsComponentFile");
+
     return true;
 }
 
@@ -52,6 +71,8 @@ bool JavascriptImporter::SaveSettingsInternal()
 
     JSONValue import = jsonRoot_.CreateChild("JavascriptImporter");
 
+    import.SetBool("IsComponentFile", isComponentFile_);
+
     return true;
 }
 

+ 4 - 0
Source/ToolCore/Assets/JavascriptImporter.h

@@ -17,10 +17,14 @@ public:
 
     virtual void SetDefaults();
 
+    bool IsComponentFile();
+
     bool Import(const String& guid);
 
 protected:
 
+    bool isComponentFile_;
+
     virtual bool LoadSettingsInternal();
     virtual bool SaveSettingsInternal();
 

+ 16 - 0
Source/ToolCore/JSBind/JSBPreprocessVisitor.h

@@ -43,6 +43,10 @@ public:
 
     virtual bool visit(Enum *penum)
     {
+        // don't want enum's in classes
+        if (classes_.Size())
+            return true;
+
         JSBModule* module = header_->GetModule();
 
         JSBEnum* jenum = new JSBEnum(header_->GetContext(), module_, getNameString(penum->name()));
@@ -63,6 +67,8 @@ public:
 
     virtual bool visit(Class *klass)
     {
+        classes_.Push(klass);
+
         String name = getNameString(klass->name());
 
         JSBModule* module = header_->GetModule();
@@ -72,11 +78,21 @@ public:
         return true;
     }
 
+    void postVisit(Symbol *symbol)
+    {
+        if (symbol->asClass())
+        {
+            classes_.Remove((Class*) symbol);
+        }
+    }
+
 private:
 
     SharedPtr<JSBHeader> header_;
     SharedPtr<JSBModule> module_;
 
+    PODVector<Class*> classes_;
+
     Namespace* globalNamespace_;
 
 };