Browse Source

Merge pull request #1288 from AtomicGameEngine/JME-ATOMIC-1236

[C#] On demand editor solution compilation, including play mode
JoshEngebretson 9 years ago
parent
commit
426e7b0d28

+ 8 - 0
Script/AtomicEditor/editor/EditorEvents.ts

@@ -181,3 +181,11 @@ export interface UserPreferencesChangedEvent {
 
 export const WebViewLoadEnd = "WebViewLoadEnd";
 export const WebMessage = "WebMessage";
+
+// interface to pass modal error messages from core modules
+export const EditorModal = "EditorModal";
+export interface EditorModalEvent {
+    uiType: number;     // EDITOR_ERROR_MODAL, etc)
+    title: string;      // for modal errors, title text
+    message: string;    // for modal errors, error text
+}

+ 28 - 20
Script/AtomicEditor/hostExtensions/languageExtensions/CSharpLanguageExtension.ts

@@ -21,6 +21,7 @@
 //
 
 import * as EditorEvents from "../../editor/EditorEvents";
+import EditorUI = require("ui/EditorUI");
 
 /**
 * Resource extension that supports the web view typescript extension
@@ -40,6 +41,9 @@ export default class CSharpLanguageExtension implements Editor.HostExtensions.Re
     /** Reference to the compileOnSaveMenuItem */
     private compileOnSaveMenuItem: Atomic.UIMenuItem;
 
+    //** A script object so we can take part in event handling
+    private eventObject = new Atomic.ScriptObject();
+
     /**
     * Determines if the file name/path provided is something we care about
     * @param  {string} path
@@ -69,9 +73,6 @@ export default class CSharpLanguageExtension implements Editor.HostExtensions.Re
             menu.addItem(new Atomic.UIMenuItem("Generate Solution", `${this.name}.generatesolution`));
             menu.addItem(new Atomic.UIMenuItem("Package Resources", `${this.name}.packageresources`));
 
-            this.compileOnSaveMenuItem = new Atomic.UIMenuItem(`Compile on Save: ${isCompileOnSave ? "On" : "Off"}`, `${this.name}.compileonsave`);
-            menu.addItem(this.compileOnSaveMenuItem);
-
             this.menuCreated = true;
         }
 
@@ -135,15 +136,6 @@ export default class CSharpLanguageExtension implements Editor.HostExtensions.Re
             this.isNETProject = true;
         }
 
-        const isCompileOnSave = this.serviceRegistry.projectServices.getUserPreference(this.name, "CompileOnSave", false);
-
-        if (isCompileOnSave && ToolCore.netProjectSystem) {
-
-            // for now, only support compile on save when not using VS
-            if (!ToolCore.netProjectSystem.iDEAvailable)
-                ToolCore.netProjectSystem.buildAtomicProject();
-        }
-
     }
 
     /*** ProjectService implementation ****/
@@ -166,12 +158,34 @@ export default class CSharpLanguageExtension implements Editor.HostExtensions.Re
         };
 
         if (found) {
+
             this.isNETProject = true;
             this.configureNETProjectMenu();
+
+            this.eventObject.subscribeToEvent("NETBuildResult", (eventData:ToolCore.NETBuildResult) => {
+
+                if (!eventData.success) {
+
+                    let errorText = eventData.errorText;
+
+                    // attempt to shave off some of the build text
+                    // xbuild
+                    let index = errorText.lastIndexOf("Errors:");
+                    // msbuild
+                    index = index == -1 ? errorText.lastIndexOf("Build FAILED.") : index;
+
+                    if (index != -1) {
+                        errorText = errorText.substr(index);
+                    }
+
+                    EditorUI.getModelOps().showError("NET Build Error", errorText);
+
+                }
+
+            });
         }
     }
 
-
     /**
     * Called when the project is unloaded
     */
@@ -180,6 +194,7 @@ export default class CSharpLanguageExtension implements Editor.HostExtensions.Re
         this.serviceRegistry.uiServices.removePluginMenuItemSource("AtomicNET");
         this.menuCreated = false;
         this.isNETProject = false;
+        this.eventObject.unsubscribeFromAllEvents();
     }
 
     /*** UIService implementation ***/
@@ -205,13 +220,6 @@ export default class CSharpLanguageExtension implements Editor.HostExtensions.Re
                 case "packageresources":
                     this.packageResources();
                     return true;
-                case "compileonsave":
-                    let isCompileOnSave = this.serviceRegistry.projectServices.getUserPreference(this.name, "CompileOnSave", false);
-                    // Toggle
-                    isCompileOnSave = !isCompileOnSave;
-                    this.serviceRegistry.projectServices.setUserPreference(this.name, "CompileOnSave", isCompileOnSave);
-                    this.compileOnSaveMenuItem.string = `Compile on Save: ${isCompileOnSave ? "On" : "Off"}`;
-                    return true;
             }
         }
     }

+ 2 - 8
Script/AtomicEditor/ui/AnimationToolbar.ts

@@ -25,7 +25,6 @@ import EditorUI = require("ui/EditorUI");
 import HierarchyFrame = require("ui/frames/HierarchyFrame");
 import InspectorUtils = require("ui/frames/inspector/InspectorUtils");
 import ResourceOps = require("resources/ResourceOps");
-import ModalOps = require("ui/modal/ModalOps");
 
 class AnimationToolbar extends Atomic.UIWidget {
 
@@ -75,8 +74,6 @@ class AnimationToolbar extends Atomic.UIWidget {
 
         properties.addChild(this.animationPropertiesContainer);
 
-        this.modalOps = new ModalOps();
-
     }
 
     handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
@@ -112,7 +109,7 @@ class AnimationToolbar extends Atomic.UIWidget {
                     if (this.animationController.playExclusive(this.rightAnimEditfield.text, 0, true, Number(this.blendSpeed.text)))
                         this.animationController.setSpeed(this.rightAnimEditfield.text, Number(this.animationSpeed.text));
                     else
-                        this.modalOps.showError("Animation Toolbar Warning", "The animation cannot be played. Please make sure the animation you are trying to play exists in the AnimationController Component.");
+                        EditorUI.getModelOps().showError("Animation Toolbar Warning", "The animation cannot be played. Please make sure the animation you are trying to play exists in the AnimationController Component.");
 
                     return true;
                 }
@@ -192,10 +189,9 @@ class AnimationToolbar extends Atomic.UIWidget {
         });
     }
     showAnimationWarning() {
-        this.modalOps.showError("Animation Preview Warning", "The animation cannot be played. Please make sure the animation you are trying to play exists in the AnimationController Component.");
+        EditorUI.getModelOps().showError("Animation Preview Warning", "The animation cannot be played. Please make sure the animation you are trying to play exists in the AnimationController Component.");
     }
 
-    modalOps: ModalOps;
     //Animation Toolbar Widgets
     animationController: Atomic.AnimationController;
     animatedModel: Atomic.AnimatedModel;
@@ -222,5 +218,3 @@ class AnimationToolbar extends Atomic.UIWidget {
 }
 
 export = AnimationToolbar;
-
-

+ 4 - 0
Script/AtomicEditor/ui/EditorUI.ts

@@ -110,6 +110,10 @@ class EditorUI extends Atomic.ScriptObject {
     this.subscribeToEvent(EditorEvents.ModalError, (event:EditorEvents.ModalErrorEvent) => {
       this.showModalError(event.title, event.message);
     });
+    
+    this.subscribeToEvent(EditorEvents.EditorModal, (event:EditorEvents.EditorModalEvent) => {
+      this.showModalError(event.title, event.message);
+    });
 
   }
 

+ 7 - 0
Script/AtomicEditor/ui/Shortcuts.ts

@@ -39,7 +39,9 @@ class Shortcuts extends Atomic.ScriptObject {
 
     //this should be moved somewhere else...
     invokePlayOrStopPlayer(debug: boolean = false) {
+
         this.sendEvent(EditorEvents.SaveAllResources);
+
         if (Atomic.editorMode.isPlayerEnabled()) {
             this.sendEvent("IPCPlayerExitRequest");
         } else {
@@ -217,6 +219,11 @@ class Shortcuts extends Atomic.ScriptObject {
 
         var cmdKey = this.cmdKeyDown();
 
+        if ( !cmdKey && ev.qualifiers > 0 ) // check the event, the qualifier may have been programmitically set
+        {
+            cmdKey = ( ev.qualifiers == Atomic.QUAL_CTRL );
+        }
+
         if (cmdKey) {
 
             if (ev.key == Atomic.KEY_S) {

+ 2 - 2
Script/Packages/ToolCore/ToolCore.json

@@ -4,14 +4,14 @@
 							 "Source/ToolCore/Import", "Source/ToolCore/Assets", "Source/ToolCore/License", "Source/ToolCore/Build",
 						 	 "Source/ToolCore/Subprocess", "Source/ToolCore/NETTools"],
 	"classes" : ["ToolEnvironment", "ToolSystem", "ToolPrefs", "SubprocessSystem", "Subprocess",
-								"Project", "ProjectFile", "Platform", "PlatformMac", "PlatformWeb",
+								"Project", "ProjectSettings", "ProjectFile", "Platform", "PlatformMac", "PlatformWeb",
 							 "PlatformWindows", "PlatformAndroid", "PlatformIOS", "PlatformLinux", "Command", "PlayCmd", "OpenAssetImporter",
 							 "Asset", "AssetDatabase", "AssetImporter", "AudioImporter", "ModelImporter", "MaterialImporter", "AnimationImportInfo",
 							 "PrefabImporter", "JavascriptImporter", "JSONImporter",
 							 "TextureImporter", "SpriterImporter", "PEXImporter", "CSharpImporter", "NETAssemblyImporter",
 							 "LicenseSystem",
 						 	 "ProjectUserPrefs", "ProjectBuildSettings",
-							 "NETProjectSystem",
+							 "NETProjectSystem", "NETBuild",
 						 	 "BuildBase", "BuildSystem", "BuildMac", "BuildWeb", "BuildWindows", "BuildAndroid", "BuildIOS", "BuildLinux",
 						 	 "ProjectBuildSettings", "MacBuildSettings", "WindowsBuildSettings", "WebBuildSettings", "AndroidBuildSettings", "IOSBuildSettings", "LinuxBuildSettings"],
 	"typescript_decl" : {

+ 8 - 1
Script/TypeScript/AtomicWork.d.ts

@@ -270,7 +270,7 @@ declare module Atomic {
         resourceTypeName: string;
         dynamic: boolean;
         tooltip: string;
-        
+
     }
 
     export interface ShaderParameter {
@@ -432,6 +432,13 @@ declare module ToolCore {
 
     }
 
+    export interface NETBuildResult {
+
+        success: boolean;
+        build: NETBuild;
+        errorText: string;
+    }
+
     export var toolEnvironment: ToolEnvironment;
     export var toolSystem: ToolSystem;
     export var assetDatabase: AssetDatabase;

+ 1 - 1
Script/tsconfig.json

@@ -37,6 +37,7 @@
         "./AtomicEditor/resources/ProjectTemplates.ts",
         "./AtomicEditor/resources/ResourceOps.ts",
         "./AtomicEditor/resources/SearchBarFiltering.ts",
+        "./AtomicEditor/ui/AnimationToolbar.ts",
         "./AtomicEditor/ui/EditorStrings.ts",
         "./AtomicEditor/ui/EditorUI.ts",
         "./AtomicEditor/ui/frames/HierarchyFrame.ts",
@@ -71,7 +72,6 @@
         "./AtomicEditor/ui/frames/ResourceFrame.ts",
         "./AtomicEditor/ui/frames/WelcomeFrame.ts",
         "./AtomicEditor/ui/MainToolbar.ts",
-        "./AtomicEditor/ui/AnimationToolbar.ts",
         "./AtomicEditor/ui/modal/About.ts",
         "./AtomicEditor/ui/modal/build/BuildComplete.ts",
         "./AtomicEditor/ui/modal/build/BuildOutput.ts",

+ 54 - 1
Source/AtomicEditor/EditorMode/AEEditorMode.cpp

@@ -35,6 +35,9 @@
 #include <ToolCore/Project/Project.h>
 #include <ToolCore/Project/ProjectSettings.h>
 
+#include <ToolCore/NETTools/NETProjectSystem.h>
+#include <ToolCore/NETTools/NETBuildSystem.h>
+
 #include <AtomicJS/Javascript/JSIPCEvents.h>
 
 #include <Atomic/UI/SystemUI/DebugHud.h>
@@ -49,7 +52,9 @@ namespace AtomicEditor
 {
 
 EditorMode::EditorMode(Context* context) :
-    Object(context)
+    Object(context),
+    playerEnabled_(false),
+    debug_(false)
 {
 
 }
@@ -118,7 +123,55 @@ void EditorMode::HandleIPCJSError(StringHash eventType, VariantMap& eventData)
 
 }
 
+void EditorMode::HandleNETBuildResult(StringHash eventType, VariantMap& eventData)
+{
+
+    using namespace NETBuildResult;
+
+    if (eventData[P_SUCCESS].GetBool())
+    {
+        NETProjectSystem* netProjectSystem = GetSubsystem<NETProjectSystem>();
+
+        if (!netProjectSystem->GetSolutionAvailable() || netProjectSystem->GetProjectAssemblyDirty())
+        {
+            ATOMIC_LOGERROR("EditorMode::HandleNETBuildResult() - NETBuild was successful, however project still reported as dirty or missing");
+        }
+
+        PlayProjectInternal(additionalArgs_, debug_);
+    }
+
+    additionalArgs_.Clear();
+    debug_ = false;
+
+}
+
 bool EditorMode::PlayProject(String addArgs, bool debug)
+{
+    additionalArgs_ = addArgs;
+    debug_ = debug;
+
+    NETProjectSystem* netProjectSystem = GetSubsystem<NETProjectSystem>();
+
+    // If we're a net project, with a solution, and the project assembly is dirty build before playing
+    if (netProjectSystem && netProjectSystem->GetSolutionAvailable() && netProjectSystem->GetProjectAssemblyDirty())
+    {
+        NETBuild* build = netProjectSystem->BuildAtomicProject();
+
+        if (!build)
+        {
+            ATOMIC_LOGERROR("EditorMode::PlayProject() - Unable to instantiate C# build");
+            return false;
+        }
+
+        SubscribeToEvent(build, E_NETBUILDRESULT, ATOMIC_HANDLER(EditorMode, HandleNETBuildResult));
+
+        return true;
+    }
+
+    return PlayProjectInternal(addArgs, debug);
+}
+
+bool EditorMode::PlayProjectInternal(const String &addArgs, bool debug)
 {
     if (playerBroker_.NotNull())
         return false;

+ 7 - 1
Source/AtomicEditor/EditorMode/AEEditorMode.h

@@ -52,6 +52,8 @@ public:
 
 private:
 
+    bool PlayProjectInternal(const String& addArgs, bool debug);
+
     void HandleIPCWorkerStarted(StringHash eventType, VariantMap& eventData);
     void HandleIPCJSError(StringHash eventType, VariantMap& eventData);
     void HandleIPCWorkerLog(StringHash eventType, VariantMap& eventData);
@@ -61,9 +63,13 @@ private:
     void HandleIPCPlayerPauseStepRequest(StringHash eventType, VariantMap& eventData);
     void HandleIPCPlayerExitRequest(StringHash eventType, VariantMap& eventData);
 
+    void HandleNETBuildResult(StringHash eventType, VariantMap& eventData);
+
     SharedPtr<IPCBroker> playerBroker_;
 
-    bool playerEnabled_ = false;
+    bool playerEnabled_;
+    String additionalArgs_;
+    bool debug_;
 
 };
 

+ 8 - 0
Source/ToolCore/NETTools/NETBuildSystem.cpp

@@ -44,6 +44,14 @@
 
 namespace ToolCore
 {
+    NETBuild::NETBuild(Context* context, const String& solutionPath) :
+        Object(context),
+        solutionPath_(solutionPath),
+        status_(NETBUILD_PENDING)
+    {
+
+    }
+
 
     NETBuild::NETBuild(Context* context, const String& solutionPath, const StringVector& platforms, const StringVector& configurations) :
         Object(context),

+ 1 - 0
Source/ToolCore/NETTools/NETBuildSystem.h

@@ -62,6 +62,7 @@ namespace ToolCore
     
     public:
 
+        NETBuild(Context* context, const String& solutionPath = String::EMPTY);
         NETBuild(Context* context, const String& solutionPath, const StringVector& platforms, const StringVector& configurations);
         virtual ~NETBuild() {}
 

+ 71 - 8
Source/ToolCore/NETTools/NETProjectSystem.cpp

@@ -192,7 +192,7 @@ namespace ToolCore
 
     }
 
-    void NETProjectSystem::BuildAtomicProject()
+    NETBuild* NETProjectSystem::BuildAtomicProject()
     {
         FileSystem* fileSystem = GetSubsystem<FileSystem>();
 
@@ -201,7 +201,7 @@ namespace ToolCore
             if (!GenerateSolution())
             {
                 ATOMIC_LOGERRORF("NETProjectSystem::BuildAtomicProject - solutionPath does not exist: %s", solutionPath_.CString());
-                return;
+                return nullptr;
             }
         }
 
@@ -217,7 +217,10 @@ namespace ToolCore
                 build->SubscribeToEvent(E_NETBUILDRESULT, ATOMIC_HANDLER(NETProjectSystem, HandleNETBuildResult));
             }
 
+            return build;
         }
+
+        return nullptr;
     }
 
     bool NETProjectSystem::GenerateResourcePak()
@@ -358,8 +361,7 @@ namespace ToolCore
         if (!fileSystem->FileExists(solutionPath_))
             solutionDirty_ = true;
 
-        if (!fileSystem->FileExists(projectAssemblyPath_))
-            projectAssemblyDirty_ = true;
+        CheckProjectAssembly();
 
     }
 
@@ -421,6 +423,71 @@ namespace ToolCore
 
     }
 
+    bool NETProjectSystem::GetProjectAssemblyDirty()
+    {
+        if (projectAssemblyDirty_)
+            return true;
+
+        CheckProjectAssembly();
+
+        return projectAssemblyDirty_;
+
+    }
+    
+    void NETProjectSystem::CheckProjectAssembly()
+    {
+        FileSystem* fileSystem = GetSubsystem<FileSystem>();
+        ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+        Project* project = tsystem->GetProject();
+
+        if (!project || !projectAssemblyPath_.Length())
+        {
+            ATOMIC_LOGERROR("NETProjectSystem::CheckProjectAssembly() - Called with no project loaded or an empty project assembly path");
+            projectAssemblyDirty_ = false;
+            return;
+        }
+
+        if (projectAssemblyDirty_)
+            return;
+
+        // If we don't have a project or the project assembly is missing, we must rebuild
+        if (!fileSystem->FileExists(projectAssemblyPath_))
+        {
+            projectAssemblyDirty_ = true;
+            return;
+        }
+
+        StringVector results;
+
+        // timestamps for present assemblies, use the filesystem and not asset db as the later is cached
+        PODVector<unsigned> assemblyTimestamps;
+
+        fileSystem->ScanDir(results, project->GetResourcePath(), "*.dll", SCAN_FILES, true);
+
+        for (unsigned i = 0; i < results.Size(); i++)
+        {
+            assemblyTimestamps.Push(fileSystem->GetLastModifiedTime(project->GetResourcePath() + results[i]));
+        }
+
+        fileSystem->ScanDir(results, project->GetResourcePath(), "*.cs", SCAN_FILES, true);
+
+        for (unsigned i = 0; i < results.Size(); i++)
+        {
+            unsigned timestamp = fileSystem->GetLastModifiedTime(project->GetResourcePath() + results[i]);
+
+            for (unsigned j = 0; j < assemblyTimestamps.Size(); j++)
+            {
+                if (timestamp > assemblyTimestamps[j])
+                {
+                    projectAssemblyDirty_ = true;
+                    return;
+                }
+            }
+
+        }
+
+    }
+    
     void NETProjectSystem::Initialize()
     {
         Clear();
@@ -535,12 +602,9 @@ namespace ToolCore
 
                 if (srcModifiedTime == fileSystem->GetLastModifiedTime(dstFile))
                 {
-                    ATOMIC_LOGDEBUGF("NETProjectSystem::CopyAtomicAssemblies - Skipping AtomicNET %s as %s exists and has same modified time", srcFile.CString(), dstFile.CString());
                     continue;
                 }
 
-                ATOMIC_LOGDEBUGF("NETProjectSystem::CopyAtomicAssemblies - %u %u", srcModifiedTime, fileSystem->GetLastModifiedTime(dstFile));
-
             }
 
             String dstPath = GetPath(dstFile);
@@ -561,7 +625,6 @@ namespace ToolCore
             if (srcModifiedTime)
                 fileSystem->SetLastModifiedTime(dstFile, srcModifiedTime);
 
-            ATOMIC_LOGDEBUGF(" NETProjectSystem::CopyAtomicAssemblies - Copied AtomicNET %s to %s", srcFile.CString(), dstPath.CString());
         }
 
         return true;

+ 8 - 1
Source/ToolCore/NETTools/NETProjectSystem.h

@@ -31,6 +31,7 @@ namespace ToolCore
 
     class Project;
     class Subprocess;
+    class NETBuild;
 
     // AtomicProject.dll state (this shouldn't be in resources too)
 
@@ -57,7 +58,7 @@ namespace ToolCore
 
         const String& GetSolutionPath() const { return solutionPath_; }
 
-        void BuildAtomicProject();
+        NETBuild* BuildAtomicProject();
 
         /// Open the solution, if opening a source file, better to call OpenSourceFile as will launch VS instance with source file loaded
         /// otherwise, no guarantee where source file will load when multiple VS instances running
@@ -68,6 +69,9 @@ namespace ToolCore
         bool GenerateSolution();
         bool GenerateResourcePak();
 
+        /// Get whether the project assembly is dirty and needs to be rebuilt
+        bool GetProjectAssemblyDirty();
+
     private:
 
         void HandleUpdate(StringHash eventType, VariantMap& eventData);
@@ -90,6 +94,9 @@ namespace ToolCore
         void Clear();
         void Initialize();
 
+        /// Checks the status of the project assembly, including if sources files are newer, missing, etc
+        void CheckProjectAssembly();
+
         String idePath_;
 
         String projectPath_;