Browse Source

Scripted component classes

Josh Engebretson 10 years ago
parent
commit
e52640b95f

+ 6 - 0
Script/TypeScript/Atomic.d.ts

@@ -8031,14 +8031,20 @@ declare module Atomic {
       // Return whether the DelayedStart() function has been called.
       isDelayedStartCalled(): boolean;
       setComponentFile(cfile: JSComponentFile, loading?: boolean): void;
+      setDestroyed(): void;
+      initInstance(hasArgs?: boolean, argIdx?: number): void;
 
    }
 
    export class JSComponentFile extends Resource {
 
+      scriptClass: boolean;
+
       // Construct.
       constructor();
 
+      getScriptClass(): boolean;
+      pushModule(): boolean;
 
    }
 

+ 62 - 38
Source/AtomicJS/Javascript/JSComponent.cpp

@@ -51,6 +51,7 @@ JSComponent::JSComponent(Context* context) :
     currentEventMask_(0),
     started_(false),
     destroyed_(false),
+    scriptClassInstance_(false),
     delayedStartCalled_(false),
     loading_(false)
 {
@@ -120,42 +121,53 @@ void JSComponent::UpdateReferences(bool remove)
 
 void JSComponent::ApplyAttributes()
 {
-    if (!started_)
+    if (scriptClassInstance_)
+        return;
+
+    // coming in from a load, so we may have a scripted component
+
+    if (!context_->GetEditorContext() && componentFile_.NotNull())
     {
-        InitModule();
+        if (componentFile_->GetScriptClass())
+        {
+            unsigned id = this->GetID();
+
+            SharedPtr<Node> node(node_);
+            SharedPtr<Component> keepAlive(this);
+            node_->RemoveComponent(this);
+
+            duk_context* ctx = vm_->GetJSContext();
+            componentFile_->PushModule();
+            duk_new(ctx, 0);
+            if (duk_is_object(ctx, -1))
+            {
+                JSComponent* component = js_to_class_instance<JSComponent>(ctx, -1, 0);
+                component->scriptClassInstance_ = true;
+                component->fieldValues_ = fieldValues_;
+                component->SetComponentFile(componentFile_);
+                node->AddComponent(component, id, REPLICATED);
+                component->InitInstance();
+            }
+
+            duk_pop(ctx);
+
+            return;
+        }
     }
+
+    // not a class component
+    InitInstance();
 }
 
-void JSComponent::InitModule(bool hasArgs, int argIdx)
+void JSComponent::InitInstance(bool hasArgs, int argIdx)
 {
     if (context_->GetEditorContext() || componentFile_.Null())
         return;
 
     duk_context* ctx = vm_->GetJSContext();
 
-    const String& path = componentFile_->GetName();
-    String pathName, fileName, ext;
-    SplitPath(path, pathName, fileName, ext);
-
-    pathName += "/" + fileName;
-
     duk_idx_t top = duk_get_top(ctx);
 
-    duk_get_global_string(ctx, "require");
-    duk_push_string(ctx, pathName.CString());
-
-    if (duk_pcall(ctx, 1) != 0)
-    {
-        vm_->SendJSErrorEvent();
-        return;
-    }
-
-    if (!duk_is_object(ctx, -1))
-    {
-        duk_set_top(ctx, top);
-        return;
-    }
-
     // store, so pop doesn't clear
     UpdateReferences();
 
@@ -215,22 +227,28 @@ void JSComponent::InitModule(bool hasArgs, int argIdx)
 
     }
 
-
-    duk_get_prop_string(ctx, -1, "component");
-    if (!duk_is_function(ctx, -1))
+    if (!componentFile_->GetScriptClass())
     {
-        duk_set_top(ctx, top);
-        return;
-    }
 
-    // call with self
-    js_push_class_object_instance(ctx, this, "JSComponent");
+        componentFile_->PushModule();
+
+        duk_get_prop_string(ctx, -1, "component");
+        if (!duk_is_function(ctx, -1))
+        {
+            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;
+        }
 
-    if (duk_pcall(ctx, 1) != 0)
-    {
-        vm_->SendJSErrorEvent();
-        duk_set_top(ctx, top);
-        return;
     }
 
     duk_set_top(ctx, top);
@@ -264,10 +282,16 @@ void JSComponent::CallScriptMethod(const String& name, bool passValue, float val
         return;
     }
 
+    // push this
+    if (scriptClassInstance_)
+        duk_push_heapptr(ctx, heapptr);
+
     if (passValue)
         duk_push_number(ctx, value);
 
-    if (duk_pcall(ctx, passValue ? 1 : 0) != 0)
+    int status = scriptClassInstance_ ? duk_pcall_method(ctx, passValue ? 1 : 0) : duk_pcall(ctx, passValue ? 1 : 0);
+
+    if (status != 0)
     {
         vm_->SendJSErrorEvent();
         duk_set_top(ctx, top);

+ 2 - 1
Source/AtomicJS/Javascript/JSComponent.h

@@ -84,7 +84,7 @@ public:
 
     void SetDestroyed() { destroyed_ = true; }
 
-    void InitModule(bool hasArgs = false, int argIdx = 0);
+    void InitInstance(bool hasArgs = false, int argIdx = 0);
 
 protected:
     /// Handle scene node being assigned at creation.
@@ -130,6 +130,7 @@ private:
 
     bool started_;
     bool destroyed_;
+    bool scriptClassInstance_;
 
     /// Flag for delayed start.
     bool delayedStartCalled_;

+ 89 - 3
Source/AtomicJS/Javascript/JSComponentFile.cpp

@@ -34,7 +34,8 @@ namespace Atomic
 {
 
 JSComponentFile::JSComponentFile(Context* context) :
-    Resource(context)
+    Resource(context),
+    scriptClass_(false)
 {
 }
 
@@ -86,9 +87,75 @@ void JSComponentFile::GetDefaultFieldValue(const String& name, Variant& v)
 
 }
 
+bool JSComponentFile::PushModule()
+{
+    if (context_->GetEditorContext())
+        return false;
+
+    JSVM* vm = JSVM::GetJSVM(NULL);
+
+    duk_context* ctx = vm->GetJSContext();
+
+    const String& path = GetName();
+    String pathName, fileName, ext;
+    SplitPath(path, pathName, fileName, ext);
+
+    pathName += "/" + fileName;
+
+    duk_get_global_string(ctx, "require");
+    duk_push_string(ctx, pathName.CString());
+
+    if (duk_pcall(ctx, 1) != 0)
+    {
+        vm->SendJSErrorEvent();
+        return false;
+    }
+
+    return true;
+
+}
+
+bool JSComponentFile::InitModule()
+{
+    if (context_->GetEditorContext())
+        return true;
+
+    JSVM* vm = JSVM::GetJSVM(NULL);
+
+    duk_context* ctx = vm->GetJSContext();
+
+    duk_idx_t top = duk_get_top(ctx);
+
+    if (!PushModule())
+        return false;
+
+    if (duk_is_function(ctx, -1))
+    {
+        // TODO: verify JSComponent in prototype chain
+        scriptClass_ = true;
+        duk_set_top(ctx, top);
+        return true;
+    }
+
+    if (!duk_is_object(ctx, -1))
+    {
+        duk_set_top(ctx, top);
+        return false;
+    }
+
+    duk_set_top(ctx, top);
+
+    return true;
+}
+
+
 bool JSComponentFile::BeginLoad(Deserializer& source)
 {
+    if (!InitModule())
+        return false;
+
     // TODO: cache these for player builds
+    // FIXME: this won't work with obfusication or minimization
 
     unsigned dataSize = source.GetSize();
     if (!dataSize && !source.GetName().Empty())
@@ -116,15 +183,34 @@ bool JSComponentFile::BeginLoad(Deserializer& source)
         {
             line = line.Trimmed();
 
-            if (line.StartsWith("exports.fields"))
+            if (line.StartsWith("inspectorFields"))
             {
-                eval = line.Substring(8);
+                eval = line;
                 if (line.Contains("}"))
                 {
                     valid = true;
                     break;
                 }
             }
+            else if (line.StartsWith("this.inspectorFields"))
+            {
+                eval = line.Substring(5);
+                if (line.Contains("}"))
+                {
+                    valid = true;
+                    break;
+                }
+            }
+            else if (line.StartsWith("var inspectorFields"))
+            {
+                eval = line.Substring(4);
+                if (line.Contains("}"))
+                {
+                    valid = true;
+                    break;
+                }
+            }
+
         }
         else
         {

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

@@ -48,10 +48,16 @@ public:
     /// Save resource
     virtual bool Save(Serializer& dest) const;
 
+    bool GetScriptClass() { return scriptClass_; }
+
+    bool PushModule();
     void GetDefaultFieldValue(const String& name, Variant& v);
 
 private:
 
+    bool InitModule();
+
+    bool scriptClass_;
     HashMap<String, VariantType> fields_;
     VariantMap defaultFieldValues_;
 

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

@@ -36,7 +36,7 @@ static int Node_CreateJSComponent(duk_context* ctx)
     JSComponentFile* file = cache->GetResource<JSComponentFile>(path);
 
     jsc->SetComponentFile(file);
-    jsc->InitModule(hasArgs, argIdx);
+    jsc->InitInstance(hasArgs, argIdx);
 
     js_push_class_object_instance(ctx, jsc, "JSComponent");
     return 1;

+ 7 - 1
Source/AtomicJS/Packages/Atomic/Javascript.json

@@ -1,5 +1,11 @@
 {
 	"name" : "Javascript",
 	"sources" : ["Source/AtomicJS/Javascript"],
-	"classes" : ["JSVM", "JSComponent", "JSComponentFile", "JSMetrics", "JSEventHelper", "ScriptObject"]
+	"classes" : ["JSVM", "JSComponent", "JSComponentFile", "JSMetrics", "JSEventHelper", "ScriptObject"],
+	"typescript_decl" : {
+
+		"JSComponent" : [
+		]
+	}
+
 }