Sfoglia il codice sorgente

Asynchronous loading of ScriptFile. Due to possibly accessing arbitrary engine functionality in script initializers the actual script compilation cannot be threaded even if AngelScript itself is compiled as thread-safe. Furthermore AngelScript allows only 1 simultaneous compile, and the resource loading system is not prepared to fail and retry in that case.

Lasse Öörni 11 anni fa
parent
commit
21053067bd
2 ha cambiato i file con 55 aggiunte e 28 eliminazioni
  1. 48 28
      Source/Engine/Script/ScriptFile.cpp
  2. 7 0
      Source/Engine/Script/ScriptFile.h

+ 48 - 28
Source/Engine/Script/ScriptFile.cpp

@@ -21,11 +21,11 @@
 //
 
 #include "Precompiled.h"
-#include "ArrayPtr.h"
 #include "Context.h"
 #include "CoreEvents.h"
 #include "FileSystem.h"
 #include "Log.h"
+#include "MemoryBuffer.h"
 #include "Profiler.h"
 #include "ResourceCache.h"
 #include "Script.h"
@@ -72,7 +72,7 @@ class ByteCodeDeserializer : public asIBinaryStream
 {
 public:
     /// Construct.
-    ByteCodeDeserializer(Deserializer& source) :
+    ByteCodeDeserializer(MemoryBuffer& source) :
         source_(source)
     {
     }
@@ -90,7 +90,7 @@ public:
     
 private:
     /// Source stream.
-    Deserializer& source_;
+    MemoryBuffer& source_;
 };
 
 ScriptFile::ScriptFile(Context* context) :
@@ -116,6 +116,8 @@ bool ScriptFile::BeginLoad(Deserializer& source)
 {
     ReleaseModule();
     
+    loadByteCode_.Reset();
+
     // Create the module. Discard previous module if there was one
     asIScriptEngine* engine = script_->GetScriptEngine();
     scriptModule_ = engine->GetModule(GetName().CString(), asGM_ALWAYS_CREATE);
@@ -128,40 +130,58 @@ bool ScriptFile::BeginLoad(Deserializer& source)
     // Check if this file is precompiled bytecode
     if (source.ReadFileID() == "ASBC")
     {
-        ByteCodeDeserializer deserializer = ByteCodeDeserializer(source);
+        // Perform actual parsing in EndLoad(); read data now
+        loadByteCodeSize_ = source.GetSize() - source.GetPosition();
+        loadByteCode_ = new unsigned char[loadByteCodeSize_];
+        source.Read(loadByteCode_.Get(), loadByteCodeSize_);
+        return true;
+    }
+    else
+        source.Seek(0);
+    
+    // Not bytecode: add the initial section and check for includes.
+    // Perform actual building during EndLoad(), as AngelScript can not multithread module compilation,
+    // and static initializers may access arbitrary engine functionality which may not be thread-safe
+    return AddScriptSection(engine, source);
+}
+
+bool ScriptFile::EndLoad()
+{
+    bool success = false;
+
+    // Load from bytecode if available, else compile
+    if (loadByteCode_)
+    {
+        MemoryBuffer buffer(loadByteCode_.Get(), loadByteCodeSize_);
+        ByteCodeDeserializer deserializer = ByteCodeDeserializer(buffer);
+
         if (scriptModule_->LoadByteCode(&deserializer) >= 0)
         {
             LOGINFO("Loaded script module " + GetName() + " from bytecode");
-            compiled_ = true;
-            // Map script module to script resource with userdata
-            scriptModule_->SetUserData(this);
-            
-            return true;
+            success = true;
         }
-        else
-            return false;
     }
     else
-        source.Seek(0);
-    
-    // Not bytecode: add the initial section and check for includes
-    if (!AddScriptSection(engine, source))
-        return false;
-    
-    // Compile
-    int result = scriptModule_->Build();
-    if (result < 0)
     {
-        LOGERROR("Failed to compile script module " + GetName());
-        return false;
+        int result = scriptModule_->Build();
+        if (result >= 0)
+        {
+            LOGINFO("Compiled script module " + GetName());
+            success = true;
+        }
+        else
+            LOGERROR("Failed to compile script module " + GetName());
     }
     
-    LOGINFO("Compiled script module " + GetName());
-    compiled_ = true;
-    // Map script module to script resource with userdata
-    scriptModule_->SetUserData(this);
-    
-    return true;
+    if (success)
+    {
+        compiled_ = true;
+        // Map script module to script resource with userdata
+        scriptModule_->SetUserData(this);
+    }
+
+    loadByteCode_.Reset();
+    return success;
 }
 
 void ScriptFile::AddEventHandler(StringHash eventType, const String& handlerName)

+ 7 - 0
Source/Engine/Script/ScriptFile.h

@@ -22,6 +22,7 @@
 
 #pragma once
 
+#include "ArrayPtr.h"
 #include "HashSet.h"
 #include "Resource.h"
 #include "ScriptEventListener.h"
@@ -56,6 +57,8 @@ public:
     
     /// Load resource from stream. May be called from a worker thread. Return true if successful.
     virtual bool BeginLoad(Deserializer& source);
+    /// Finish resource loading. Always called from the main thread. Return true if successful.
+    virtual bool EndLoad();
 
     /// Add a scripted event handler.
     virtual void AddEventHandler(StringHash eventType, const String& handlerName);
@@ -132,6 +135,10 @@ private:
     Vector<DelayedCall> delayedCalls_;
     /// Event helper objects for handling procedural or non-ScriptInstance script events
     HashMap<asIScriptObject*, SharedPtr<ScriptEventInvoker> > eventInvokers_;
+    /// Byte code for asynchronous loading.
+    SharedArrayPtr<unsigned char> loadByteCode_;
+    /// Byte code size for asynchronous loading.
+    unsigned loadByteCodeSize_;
 };
 
 /// Helper class for forwarding events to script objects that are not part of a scene.