You're breathtaking!
This commit is contained in:
259
Source/Editor/Scripting/CodeEditor.cpp
Normal file
259
Source/Editor/Scripting/CodeEditor.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CodeEditor.h"
|
||||
#include "CodeEditors/SystemDefaultCodeEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "ScriptsBuilder.h"
|
||||
#include "CodeEditors/VisualStudioCodeEditor.h"
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
#include "CodeEditors/VisualStudio/VisualStudioEditor.h"
|
||||
#endif
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Platform/Thread.h"
|
||||
#include "Engine/Threading/IRunnable.h"
|
||||
|
||||
void OnAsyncBegin(Thread* thread);
|
||||
void OnAsyncEnd();
|
||||
|
||||
class AsyncOpenTask : public IRunnable
|
||||
{
|
||||
private:
|
||||
|
||||
bool _isSolutionOpenTask;
|
||||
String _path;
|
||||
int32 _line;
|
||||
CodeEditor* _editor;
|
||||
|
||||
public:
|
||||
|
||||
AsyncOpenTask(const String& path, int32 line, CodeEditor* editor)
|
||||
: _isSolutionOpenTask(false)
|
||||
, _path(path)
|
||||
, _line(line)
|
||||
, _editor(editor)
|
||||
{
|
||||
}
|
||||
|
||||
AsyncOpenTask(CodeEditor* editor)
|
||||
: _isSolutionOpenTask(true)
|
||||
, _line(0)
|
||||
, _editor(editor)
|
||||
{
|
||||
}
|
||||
|
||||
static void OpenSolution(CodeEditor* editor)
|
||||
{
|
||||
auto task = New<AsyncOpenTask>(editor);
|
||||
auto thread = Thread::Create(task, task->ToString(), ThreadPriority::BelowNormal);
|
||||
if (thread == nullptr)
|
||||
{
|
||||
Delete(task);
|
||||
LOG(Error, "Failed to start a thread to open solution");
|
||||
return;
|
||||
}
|
||||
OnAsyncBegin(thread);
|
||||
}
|
||||
|
||||
static void OpenFile(CodeEditor* editor, const String& filePath, int32 line)
|
||||
{
|
||||
auto task = New<AsyncOpenTask>(filePath, line, editor);
|
||||
auto thread = Thread::Create(task, task->ToString(), ThreadPriority::BelowNormal);
|
||||
if (thread == nullptr)
|
||||
{
|
||||
Delete(task);
|
||||
LOG(Error, "Failed to start a thread to open file");
|
||||
return;
|
||||
}
|
||||
OnAsyncBegin(thread);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// [IRunnable]
|
||||
String ToString() const override
|
||||
{
|
||||
return TEXT("Code Editor open");
|
||||
}
|
||||
|
||||
int32 Run() override
|
||||
{
|
||||
if (_isSolutionOpenTask)
|
||||
{
|
||||
_editor->OpenSolution();
|
||||
}
|
||||
else
|
||||
{
|
||||
_editor->OpenFile(_path, _line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AfterWork(bool wasKilled) override
|
||||
{
|
||||
OnAsyncEnd();
|
||||
Delete(this);
|
||||
}
|
||||
};
|
||||
|
||||
class CodeEditingManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
CodeEditingManagerService()
|
||||
: EngineService(TEXT("Code Editing Manager"))
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
CodeEditingManagerService CodeEditingManagerServiceInstance;
|
||||
|
||||
Array<CodeEditor*> CodeEditors;
|
||||
Thread* AsyncOpenThread = nullptr;
|
||||
|
||||
Action CodeEditingManager::AsyncOpenBegin;
|
||||
Action CodeEditingManager::AsyncOpenEnd;
|
||||
|
||||
const Array<CodeEditor*>& CodeEditingManager::GetEditors()
|
||||
{
|
||||
return CodeEditors;
|
||||
}
|
||||
|
||||
bool CodeEditingManager::IsAsyncOpenRunning()
|
||||
{
|
||||
return AsyncOpenThread != nullptr;
|
||||
}
|
||||
|
||||
CodeEditor* CodeEditingManager::GetCodeEditor(CodeEditorTypes editorType)
|
||||
{
|
||||
for (int32 i = 0; i < CodeEditors.Count(); i++)
|
||||
{
|
||||
if (CodeEditors[i]->GetType() == editorType)
|
||||
return CodeEditors[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CodeEditingManager::OpenFile(CodeEditorTypes editorType, const String& path, int32 line)
|
||||
{
|
||||
const auto editor = GetCodeEditor(editorType);
|
||||
if (editor)
|
||||
{
|
||||
OpenFile(editor, path, line);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditingManager::OpenFile(CodeEditor* editor, const String& path, int32 line)
|
||||
{
|
||||
// Ensure that file exists
|
||||
if (!FileSystem::FileExists(path))
|
||||
return;
|
||||
|
||||
// Ensure that no async task is running
|
||||
if (IsAsyncOpenRunning())
|
||||
{
|
||||
// TODO: enqueue action and handle many actions in the queue
|
||||
LOG(Warning, "Cannot use code editor during async open action.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor->UseAsyncForOpen())
|
||||
{
|
||||
AsyncOpenTask::OpenFile(editor, path, line);
|
||||
}
|
||||
else
|
||||
{
|
||||
editor->OpenFile(path, line);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditingManager::OpenSolution(CodeEditorTypes editorType)
|
||||
{
|
||||
const auto editor = GetCodeEditor(editorType);
|
||||
if (editor)
|
||||
{
|
||||
editor->OpenSolution();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditingManager::OnFileAdded(CodeEditorTypes editorType, const String& path)
|
||||
{
|
||||
const auto editor = GetCodeEditor(editorType);
|
||||
if (editor)
|
||||
{
|
||||
editor->OnFileAdded(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditingManager::OpenSolution(CodeEditor* editor)
|
||||
{
|
||||
// Ensure that no async task is running
|
||||
if (IsAsyncOpenRunning())
|
||||
{
|
||||
// TODO: enqueue action and handle many actions in the queue
|
||||
LOG(Warning, "Cannot use code editor during async open action.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor->UseAsyncForOpen())
|
||||
{
|
||||
AsyncOpenTask::OpenSolution(editor);
|
||||
}
|
||||
else
|
||||
{
|
||||
editor->OpenSolution();
|
||||
}
|
||||
}
|
||||
|
||||
void OnAsyncBegin(Thread* thread)
|
||||
{
|
||||
ASSERT(AsyncOpenThread == nullptr);
|
||||
AsyncOpenThread = thread;
|
||||
CodeEditingManager::AsyncOpenBegin();
|
||||
}
|
||||
|
||||
void OnAsyncEnd()
|
||||
{
|
||||
ASSERT(AsyncOpenThread != nullptr);
|
||||
AsyncOpenThread = nullptr;
|
||||
CodeEditingManager::AsyncOpenEnd();
|
||||
}
|
||||
|
||||
bool CodeEditingManagerService::Init()
|
||||
{
|
||||
// Try get editors
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
VisualStudioEditor::FindEditors(&CodeEditors);
|
||||
#endif
|
||||
VisualStudioCodeEditor::FindEditors(&CodeEditors);
|
||||
CodeEditors.Add(New<SystemDefaultCodeEditor>());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CodeEditingManagerService::Dispose()
|
||||
{
|
||||
// Stop async task
|
||||
if (CodeEditingManager::IsAsyncOpenRunning())
|
||||
{
|
||||
AsyncOpenThread->Kill(true);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
CodeEditors.ClearDelete();
|
||||
}
|
||||
196
Source/Editor/Scripting/CodeEditor.h
Normal file
196
Source/Editor/Scripting/CodeEditor.h
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Types of in-build code editors.
|
||||
/// </summary>
|
||||
API_ENUM(Namespace="FlaxEditor", Attributes="HideInEditor") enum class CodeEditorTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom/external editor
|
||||
/// </summary>
|
||||
Custom,
|
||||
|
||||
/// <summary>
|
||||
/// Default program associated by the file extension on the system
|
||||
/// </summary>
|
||||
SystemDefault,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2008
|
||||
/// </summary>
|
||||
VS2008,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2010
|
||||
/// </summary>
|
||||
VS2010,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2012
|
||||
/// </summary>
|
||||
VS2012,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2013
|
||||
/// </summary>
|
||||
VS2013,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2059
|
||||
/// </summary>
|
||||
VS2015,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2017
|
||||
/// </summary>
|
||||
VS2017,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio 2019
|
||||
/// </summary>
|
||||
VS2019,
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio Code
|
||||
/// </summary>
|
||||
VSCode,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all code editors.
|
||||
/// </summary>
|
||||
class CodeEditor
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="CodeEditor"/> class.
|
||||
/// </summary>
|
||||
virtual ~CodeEditor() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the editor (used by the in-build editors).
|
||||
/// </summary>
|
||||
/// <returns>The name</returns>
|
||||
virtual CodeEditorTypes GetType() const
|
||||
{
|
||||
return CodeEditorTypes::Custom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the editor.
|
||||
/// </summary>
|
||||
/// <returns>The name</returns>
|
||||
virtual String GetName() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the file.
|
||||
/// </summary>
|
||||
/// <param name="path">The file path.</param>
|
||||
/// <param name="line">The target line (use 0 to not use it).</param>
|
||||
virtual void OpenFile(const String& path, int32 line) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the solution project.
|
||||
/// </summary>
|
||||
virtual void OpenSolution() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Called when source file gets added to the workspace. Can be used to automatically include new files into the project files.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
virtual void OnFileAdded(const String& path)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether use the asynchronous task for open solution/file.
|
||||
/// </summary>
|
||||
/// <returns>True if use async thread for open task.</returns>
|
||||
virtual bool UseAsyncForOpen() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Editor utility to managed and use different code editors. Allows to open solution and source code files.
|
||||
/// </summary>
|
||||
API_CLASS(Static, Namespace="FlaxEditor") class CodeEditingManager
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(CodeEditingManager);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets all found editors.
|
||||
/// </summary>
|
||||
/// <returns>The editors. Read-only.</returns>
|
||||
static const Array<CodeEditor*>& GetEditors();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether asynchronous open action is running in a background.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if asynchronous open action is running in a background; otherwise, <c>false</c>.</returns>
|
||||
API_PROPERTY() static bool IsAsyncOpenRunning();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the in-build code editor or null if not found.
|
||||
/// </summary>
|
||||
/// <param name="editorType">Type of the editor.</param>
|
||||
/// <returns>The editor object or null if not found.</returns>
|
||||
static CodeEditor* GetCodeEditor(CodeEditorTypes editorType);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the file. Handles async opening.
|
||||
/// </summary>
|
||||
/// <param name="editorType">The code editor type.</param>
|
||||
/// <param name="path">The file path.</param>
|
||||
/// <param name="line">The target line (use 0 to not use it).</param>
|
||||
API_FUNCTION() static void OpenFile(CodeEditorTypes editorType, const String& path, int32 line);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the file. Handles async opening.
|
||||
/// </summary>
|
||||
/// <param name="editor">The code editor.</param>
|
||||
/// <param name="path">The file path.</param>
|
||||
/// <param name="line">The target line (use 0 to not use it).</param>
|
||||
static void OpenFile(CodeEditor* editor, const String& path, int32 line);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the solution project. Handles async opening.
|
||||
/// </summary>
|
||||
/// <param name="editorType">The code editor type.</param>
|
||||
API_FUNCTION() static void OpenSolution(CodeEditorTypes editorType);
|
||||
|
||||
/// <summary>
|
||||
/// Called when source file gets added to the workspace. Can be used to automatically include new files into the project files.
|
||||
/// </summary>
|
||||
/// <param name="editorType">The code editor type.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
API_FUNCTION() static void OnFileAdded(CodeEditorTypes editorType, const String& path);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the solution project. Handles async opening.
|
||||
/// </summary>
|
||||
/// <param name="editor">The code editor.</param>
|
||||
static void OpenSolution(CodeEditor* editor);
|
||||
|
||||
/// <summary>
|
||||
/// The asynchronous open begins.
|
||||
/// </summary>
|
||||
static Action AsyncOpenBegin;
|
||||
|
||||
/// <summary>
|
||||
/// The asynchronous open ends.
|
||||
/// </summary>
|
||||
static Action AsyncOpenEnd;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SystemDefaultCodeEditor.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
|
||||
CodeEditorTypes SystemDefaultCodeEditor::GetType() const
|
||||
{
|
||||
return CodeEditorTypes::SystemDefault;
|
||||
}
|
||||
|
||||
String SystemDefaultCodeEditor::GetName() const
|
||||
{
|
||||
return TEXT("System Default");
|
||||
}
|
||||
|
||||
void SystemDefaultCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
Platform::StartProcess(path, StringView::Empty, StringView::Empty);
|
||||
}
|
||||
|
||||
void SystemDefaultCodeEditor::OpenSolution()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Basic implementation for editing source code on a target platform.
|
||||
/// </summary>
|
||||
class SystemDefaultCodeEditor : public CodeEditor
|
||||
{
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
};
|
||||
@@ -0,0 +1,796 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
// Import EnvDTE
|
||||
#pragma warning(disable : 4278)
|
||||
#pragma warning(disable : 4146)
|
||||
#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
|
||||
#pragma warning(default : 4146)
|
||||
#pragma warning(default : 4278)
|
||||
|
||||
// Import Microsoft.VisualStudio.Setup.Configuration.Native
|
||||
#include <Microsoft.VisualStudio.Setup.Configuration.Native/Setup.Configuration.h>
|
||||
#pragma comment(lib, "Microsoft.VisualStudio.Setup.Configuration.Native.lib")
|
||||
|
||||
#include "VisualStudioConnection.h"
|
||||
#include "Engine/Platform/Windows/ComPtr.h"
|
||||
|
||||
/// <summary>
|
||||
/// Handles retrying of calls that fail to access Visual Studio.
|
||||
/// This is due to the weird nature of VS when calling its methods from external code.
|
||||
/// If this message filter isn't registered some calls will just fail silently.
|
||||
/// </summary>
|
||||
class VSMessageFilter : public IMessageFilter
|
||||
{
|
||||
private:
|
||||
|
||||
LONG _refCount;
|
||||
|
||||
public:
|
||||
|
||||
VSMessageFilter()
|
||||
{
|
||||
_refCount = 0;
|
||||
}
|
||||
|
||||
virtual ~VSMessageFilter()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
DWORD __stdcall HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) override
|
||||
{
|
||||
return SERVERCALL_ISHANDLED;
|
||||
}
|
||||
|
||||
DWORD __stdcall RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType) override
|
||||
{
|
||||
if (dwRejectType == SERVERCALL_RETRYLATER)
|
||||
{
|
||||
// Retry immediatey
|
||||
return 99;
|
||||
}
|
||||
|
||||
// Cancel the call
|
||||
return -1;
|
||||
}
|
||||
|
||||
DWORD __stdcall MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType) override
|
||||
{
|
||||
return PENDINGMSG_WAITDEFPROCESS;
|
||||
}
|
||||
|
||||
// COM requirement. Returns instance of an interface of provided type.
|
||||
HRESULT __stdcall QueryInterface(REFIID iid, void** ppvObject) override
|
||||
{
|
||||
if (iid == IID_IDropTarget || iid == IID_IUnknown)
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = this;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
// COM requirement. Increments objects reference count.
|
||||
ULONG __stdcall AddRef() override
|
||||
{
|
||||
return _InterlockedIncrement(&_refCount);
|
||||
}
|
||||
|
||||
// COM requirement. Decreases the objects reference count and deletes the object if its zero.
|
||||
ULONG __stdcall Release() override
|
||||
{
|
||||
LONG count = _InterlockedDecrement(&_refCount);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper macro
|
||||
#define CHECK_VS_RESULT(target) if (FAILED(result)) return #target " failed with result: " + std::to_string((int)result);
|
||||
|
||||
#define USE_ITEM_OPERATIONS_OPEN 0
|
||||
#define USE_PROJECT_ITEM_OPEN 1
|
||||
#define USE_DOCUMENT_OPEN 0
|
||||
|
||||
class LocalBSTR
|
||||
{
|
||||
public:
|
||||
|
||||
BSTR Str;
|
||||
|
||||
public:
|
||||
|
||||
LocalBSTR()
|
||||
{
|
||||
Str = nullptr;
|
||||
}
|
||||
|
||||
LocalBSTR(const LPSTR str)
|
||||
{
|
||||
const auto wslen = MultiByteToWideChar(CP_ACP, 0, str, (int)strlen(str), nullptr, 0);
|
||||
Str = SysAllocStringLen(0, wslen);
|
||||
MultiByteToWideChar(CP_ACP, 0, str, (int)strlen(str), Str, wslen);
|
||||
}
|
||||
|
||||
LocalBSTR(const wchar_t* str)
|
||||
{
|
||||
Str = ::SysAllocString(str);
|
||||
}
|
||||
|
||||
~LocalBSTR()
|
||||
{
|
||||
if (Str)
|
||||
{
|
||||
::SysFreeString(Str);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
operator const BSTR() const
|
||||
{
|
||||
return Str;
|
||||
}
|
||||
|
||||
operator const wchar_t*() const
|
||||
{
|
||||
return Str;
|
||||
}
|
||||
};
|
||||
|
||||
namespace VisualStudio
|
||||
{
|
||||
bool SameFile(HANDLE h1, HANDLE h2)
|
||||
{
|
||||
BY_HANDLE_FILE_INFORMATION bhfi1 = { 0 };
|
||||
BY_HANDLE_FILE_INFORMATION bhfi2 = { 0 };
|
||||
|
||||
if (::GetFileInformationByHandle(h1, &bhfi1) && ::GetFileInformationByHandle(h2, &bhfi2))
|
||||
{
|
||||
return ((bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh) && (bhfi1.nFileIndexLow == bhfi2.nFileIndexLow) && (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AreFilePathsEqual(const wchar_t* path1, const wchar_t* path2)
|
||||
{
|
||||
if (wcscmp(path1, path2) == 0)
|
||||
return true;
|
||||
|
||||
HANDLE file1 = CreateFileW(path1, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
HANDLE file2 = CreateFileW(path2, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
bool result = SameFile(file1, file2);
|
||||
|
||||
CloseHandle(file1);
|
||||
CloseHandle(file2);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class ConnectionInternal
|
||||
{
|
||||
public:
|
||||
|
||||
const wchar_t* ClsID;
|
||||
LocalBSTR SolutionPath;
|
||||
|
||||
CLSID CLSID;
|
||||
ComPtr<EnvDTE::_DTE> DTE;
|
||||
|
||||
public:
|
||||
|
||||
ConnectionInternal(const wchar_t* clsID, const wchar_t* solutionPath)
|
||||
: ClsID(clsID)
|
||||
, SolutionPath(solutionPath)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return DTE != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Scans the running processes to find a running Visual Studio instance with the specified version and open solution.
|
||||
//
|
||||
// @param[in] clsID Class ID of the specific Visual Studio version we are looking for.
|
||||
// @param[in] solutionPath Path to the solution the instance needs to have open.
|
||||
// @return DTE object that may be used to interact with the Visual Studio instance, or null if
|
||||
// not found.
|
||||
static Result FindRunningInstance(ConnectionHandle connection)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<IRunningObjectTable> runningObjectTable;
|
||||
result = GetRunningObjectTable(0, &runningObjectTable);
|
||||
CHECK_VS_RESULT("VisualStudio::FindRunningInstance - GetRunningObjectTable");
|
||||
|
||||
ComPtr<IEnumMoniker> enumMoniker;
|
||||
result = runningObjectTable->EnumRunning(&enumMoniker);
|
||||
CHECK_VS_RESULT("VisualStudio::FindRunningInstance - EnumRunning");
|
||||
|
||||
ComPtr<IMoniker> dteMoniker;
|
||||
result = CreateClassMoniker(connection->CLSID, &dteMoniker);
|
||||
CHECK_VS_RESULT("VisualStudio::FindRunningInstance - CreateClassMoniker");
|
||||
|
||||
ComPtr<IMoniker> moniker;
|
||||
ULONG count = 0;
|
||||
while (enumMoniker->Next(1, &moniker, &count) == S_OK)
|
||||
{
|
||||
if (moniker->IsEqual(dteMoniker))
|
||||
{
|
||||
ComPtr<IUnknown> curObject;
|
||||
result = runningObjectTable->GetObjectW(moniker, &curObject);
|
||||
moniker.Detach();
|
||||
|
||||
if (result != S_OK)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::_DTE> dte;
|
||||
curObject->QueryInterface(__uuidof(EnvDTE::_DTE), (void**)&dte);
|
||||
|
||||
if (dte == nullptr)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
if (FAILED(dte->get_Solution(&solution)))
|
||||
continue;
|
||||
|
||||
LocalBSTR fullName;
|
||||
if (FAILED(solution->get_FullName(&fullName.Str)))
|
||||
continue;
|
||||
|
||||
if (AreFilePathsEqual(connection->SolutionPath, fullName))
|
||||
{
|
||||
// Found
|
||||
connection->DTE = dte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
// Opens a new Visual Studio instance of the specified version with the provided solution.
|
||||
//
|
||||
// @param[in] clsID Class ID of the specific Visual Studio version to start.
|
||||
// @param[in] solutionPath Path to the solution the instance needs to open.
|
||||
static Result OpenInstance(ConnectionHandle connection)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<IUnknown> newInstance = nullptr;
|
||||
result = CoCreateInstance(connection->CLSID, nullptr, CLSCTX_LOCAL_SERVER, EnvDTE::IID__DTE, (LPVOID*)&newInstance);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - CoCreateInstance");
|
||||
|
||||
newInstance->QueryInterface(&connection->DTE);
|
||||
if (connection->DTE == nullptr)
|
||||
return "Invalid DTE handle";
|
||||
|
||||
connection->DTE->put_UserControl(TRUE);
|
||||
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
result = connection->DTE->get_Solution(&solution);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - dte->get_Solution");
|
||||
|
||||
result = solution->Open(connection->SolutionPath);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - solution->Open");
|
||||
|
||||
// Wait until VS opens
|
||||
int maxWaitMs = 10 * 1000;
|
||||
int elapsedMs = 0;
|
||||
int stepTimeMs = 100;
|
||||
while (elapsedMs < maxWaitMs)
|
||||
{
|
||||
ComPtr<EnvDTE::Window> window = nullptr;
|
||||
if (SUCCEEDED(connection->DTE->get_MainWindow(&window)))
|
||||
return Result::Ok;
|
||||
|
||||
Sleep(stepTimeMs);
|
||||
elapsedMs += stepTimeMs;
|
||||
}
|
||||
|
||||
return "Visual Studio open timout";
|
||||
}
|
||||
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::ProjectItems>& projectItems, BSTR filePath)
|
||||
{
|
||||
long count;
|
||||
projectItems->get_Count(&count);
|
||||
if (count == 0)
|
||||
return nullptr;
|
||||
|
||||
for (long i = 1; i <= count; i++) // They are counting from [1..Count]
|
||||
{
|
||||
ComPtr<EnvDTE::ProjectItem> projectItem;
|
||||
projectItems->Item(_variant_t(i), &projectItem);
|
||||
if (!projectItem)
|
||||
continue;
|
||||
|
||||
short fileCount = 0;
|
||||
projectItem->get_FileCount(&fileCount);
|
||||
for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++)
|
||||
{
|
||||
_bstr_t filename;
|
||||
projectItem->get_FileNames(fileIndex, filename.GetAddress());
|
||||
|
||||
if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename))
|
||||
{
|
||||
return projectItem;
|
||||
}
|
||||
}
|
||||
|
||||
ComPtr<EnvDTE::ProjectItems> childProjectItems;
|
||||
projectItem->get_ProjectItems(&childProjectItems);
|
||||
if (childProjectItems)
|
||||
{
|
||||
ComPtr<EnvDTE::ProjectItem> result = FindItem(childProjectItems, filePath);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<EnvDTE::Projects> projects;
|
||||
result = solution->get_Projects(&projects);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
long projectsCount = 0;
|
||||
result = projects->get_Count(&projectsCount);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
|
||||
{
|
||||
ComPtr<EnvDTE::Project> project;
|
||||
result = projects->Item(_variant_t(projectIndex), &project);
|
||||
if (FAILED(result) || !project)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::ProjectItems> projectItems;
|
||||
result = project->get_ProjectItems(&projectItems);
|
||||
if (FAILED(result) || !projectItems)
|
||||
continue;
|
||||
|
||||
auto projectItem = FindItem(projectItems, filePath);
|
||||
if (projectItem)
|
||||
{
|
||||
return projectItem;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Opens a file on a specific line in a running Visual Studio instance.
|
||||
//
|
||||
// @param[in] dte DTE object retrieved from FindRunningInstance() or OpenInstance().
|
||||
// @param[in] filePath Path of the file to open. File should be a part of the VS solution.
|
||||
// @param[in] line Line on which to focus Visual Studio after the file is open.
|
||||
static Result OpenFile(ConnectionHandle handle, BSTR filePath, unsigned int line)
|
||||
{
|
||||
HRESULT result;
|
||||
LocalBSTR viewKind(EnvDTE::vsViewKindPrimary);
|
||||
|
||||
#if USE_ITEM_OPERATIONS_OPEN
|
||||
ComPtr<EnvDTE::ItemOperations> itemOperations;
|
||||
result = handle->DTE->get_ItemOperations(&itemOperations);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->get_ItemOperations");
|
||||
#endif
|
||||
|
||||
// Check if that file is opened
|
||||
VARIANT_BOOL isOpen = 0;
|
||||
#if USE_ITEM_OPERATIONS_OPEN
|
||||
result = itemOperations->IsFileOpen(filePath, viewKind.Str, &isOpen);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - itemOperations->IsFileOpen");
|
||||
#else
|
||||
result = handle->DTE->get_IsOpenFile(viewKind.Str, filePath, &isOpen);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->get_IsOpenFile");
|
||||
#endif
|
||||
|
||||
// Open or navigate to a window with a file
|
||||
ComPtr<EnvDTE::Window> window;
|
||||
ComPtr<EnvDTE::Document> document;
|
||||
if (isOpen == 0)
|
||||
{
|
||||
// Open file
|
||||
#if USE_DOCUMENT_OPEN
|
||||
ComPtr<EnvDTE::Documents> documents;
|
||||
result = handle->DTE->get_Documents(&documents);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - DTE->get_Documents");
|
||||
|
||||
ComPtr<EnvDTE::Document> tmp;
|
||||
LocalBSTR kind(_T("Auto"));
|
||||
result = documents->Open(filePath, kind.Str, VARIANT_FALSE, &tmp);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - documents->Open");
|
||||
|
||||
result = tmp->get_ActiveWindow(&window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - tmp->get_ActiveWindow");
|
||||
#elif USE_PROJECT_ITEM_OPEN
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
result = handle->DTE->get_Solution(&solution);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - DTE->get_Solution");
|
||||
|
||||
ComPtr<EnvDTE::ProjectItem> projectItem = FindItem(solution, filePath);
|
||||
if (projectItem)
|
||||
{
|
||||
result = projectItem->Open(viewKind, &window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - projectItem->Open");
|
||||
}
|
||||
#elif USE_ITEM_OPERATIONS_OPEN
|
||||
result = itemOperations->OpenFile(filePath, viewKind, &window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - itemOperations->OpenFile");
|
||||
#else
|
||||
result = handle->DTE->OpenFile(viewKind, filePath, &window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->OpenFile");
|
||||
#endif
|
||||
if (window == nullptr)
|
||||
return Result::Ok;
|
||||
|
||||
// Activate window and get document handle
|
||||
result = window->Activate();
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - window->Activate");
|
||||
result = handle->DTE->get_ActiveDocument(&document);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - dte->get_ActiveDocument");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find opened document
|
||||
|
||||
ComPtr<EnvDTE::Documents> documents;
|
||||
result = handle->DTE->get_Documents(&documents);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->get_Documents");
|
||||
|
||||
long documentsCount;
|
||||
result = documents->get_Count(&documentsCount);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - documents->get_Count");
|
||||
|
||||
for (int i = 1; i <= documentsCount; i++) // They are counting from [1..Count]
|
||||
{
|
||||
ComPtr<EnvDTE::Document> tmp;
|
||||
result = documents->Item(_variant_t(i), &tmp);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - documents->Item");
|
||||
if (tmp == nullptr)
|
||||
continue;
|
||||
|
||||
BSTR tmpPath;
|
||||
result = tmp->get_FullName(&tmpPath);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - tmp->get_FullName");
|
||||
|
||||
if (AreFilePathsEqual(filePath, tmpPath))
|
||||
{
|
||||
result = tmp->Activate();
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - tmp->Activate");
|
||||
|
||||
// Found
|
||||
document = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (document == nullptr)
|
||||
return "Cannot open a file";
|
||||
|
||||
// Check if need to select a given line
|
||||
if (line != 0)
|
||||
{
|
||||
ComPtr<IDispatch> selection;
|
||||
result = document->get_Selection(&selection);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - activeDocument->get_Selection");
|
||||
if (selection == nullptr)
|
||||
return Result::Ok;
|
||||
|
||||
ComPtr<EnvDTE::TextSelection> textSelection;
|
||||
result = selection->QueryInterface(&textSelection);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - selection->QueryInterface");
|
||||
|
||||
textSelection->GotoLine(line, VARIANT_TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
// Bring the window in focus
|
||||
window = nullptr;
|
||||
if (SUCCEEDED(dte->get_MainWindow(&window)))
|
||||
{
|
||||
window->Activate();
|
||||
|
||||
HWND hWnd;
|
||||
window->get_HWnd((LONG*)&hWnd);
|
||||
SetForegroundWindow(hWnd);
|
||||
}
|
||||
*/
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
// Adds a file to the project opened in a running Visual Studio instance.
|
||||
//
|
||||
// @param[in] dte DTE object retrieved from FindRunningInstance() or OpenInstance().
|
||||
// @param[in] filePath Path of the file to add.
|
||||
// @param[in] localPath Path of the file to add relative to the solution folder.
|
||||
static Result AddFile(ConnectionHandle handle, BSTR filePath, const wchar_t* localPath)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
result = handle->DTE->get_Solution(&solution);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - DTE->get_Solution");
|
||||
|
||||
ComPtr<EnvDTE::ProjectItem> projectItem = FindItem(solution, filePath);
|
||||
if (projectItem)
|
||||
{
|
||||
// Already added
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
ComPtr<EnvDTE::Projects> projects;
|
||||
result = solution->get_Projects(&projects);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
long projectsCount = 0;
|
||||
result = projects->get_Count(&projectsCount);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
ComPtr<EnvDTE::Project> project;
|
||||
wchar_t buffer[500];
|
||||
|
||||
// Place .Build.cs scripts into BuildScripts project
|
||||
const int localPathLength = (int)wcslen(localPath);
|
||||
if (localPathLength >= 10 && _wcsicmp(localPath + localPathLength - ARRAY_COUNT(".Build.cs") + 1, TEXT(".Build.cs")) == 0)
|
||||
{
|
||||
for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
|
||||
{
|
||||
result = projects->Item(_variant_t(projectIndex), &project);
|
||||
if (FAILED(result) || !project)
|
||||
continue;
|
||||
|
||||
_bstr_t name;
|
||||
if (SUCCEEDED(project->get_Name(name.GetAddress())) && wcscmp(name, TEXT("BuildScripts")) == 0)
|
||||
break;
|
||||
project = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: find the project to add script to? for .cs scripts it should be csproj, for c++ vcproj?
|
||||
|
||||
// Find parent folder
|
||||
wchar_t* path = filePath + wcslen(localPath) + 1;
|
||||
int length = (int)wcslen(path);
|
||||
memcpy(buffer, path, length * sizeof(wchar_t));
|
||||
for (int i = length - 1; i >= 0; i--)
|
||||
{
|
||||
if (buffer[i] == '\\' || buffer[i] == '/')
|
||||
{
|
||||
buffer[i] = '\0';
|
||||
length = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const LocalBSTR parentPath(TEXT("MyProject"));
|
||||
projectItem = FindItem(solution, parentPath);
|
||||
// ...
|
||||
}
|
||||
|
||||
// TODO: add file and all missing parent folders to the project
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
class CleanupHelper
|
||||
{
|
||||
public:
|
||||
|
||||
IMessageFilter* oldFilter;
|
||||
VSMessageFilter* newFilter;
|
||||
|
||||
CleanupHelper()
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
newFilter = new VSMessageFilter();
|
||||
CoRegisterMessageFilter(newFilter, &oldFilter);
|
||||
}
|
||||
|
||||
~CleanupHelper()
|
||||
{
|
||||
CoRegisterMessageFilter(oldFilter, nullptr);
|
||||
CoUninitialize();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
VisualStudio::Result VisualStudio::Result::Ok;
|
||||
|
||||
int VisualStudio::GetVisualStudioVersions(InstanceInfo* infos, int infosCount)
|
||||
{
|
||||
// Try to create the CoCreate the class; if that fails, likely no instances are registered
|
||||
ComPtr<ISetupConfiguration2> query;
|
||||
HRESULT result = CoCreateInstance(__uuidof(SetupConfiguration), nullptr, CLSCTX_ALL, __uuidof(ISetupConfiguration2), (LPVOID*)&query);
|
||||
if (FAILED(result))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the enumerator
|
||||
ComPtr<IEnumSetupInstances> enumSetupInstances;
|
||||
result = query->EnumAllInstances(&enumSetupInstances);
|
||||
if (FAILED(result))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check the state and version of the enumerated instances
|
||||
int32 count = 0;
|
||||
ComPtr<ISetupInstance> instance;
|
||||
while(count < infosCount)
|
||||
{
|
||||
ULONG fetched = 0;
|
||||
result = enumSetupInstances->Next(1, &instance, &fetched);
|
||||
if (FAILED(result) || fetched == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ComPtr<ISetupInstance2> setupInstance2;
|
||||
result = instance->QueryInterface(__uuidof(ISetupInstance2), (LPVOID*)&setupInstance2);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
InstanceState state;
|
||||
result = setupInstance2->GetState(&state);
|
||||
if (SUCCEEDED(result) && (state & eLocal) != 0)
|
||||
{
|
||||
BSTR installationVersion;
|
||||
result = setupInstance2->GetInstallationVersion(&installationVersion);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
BSTR installationPath;
|
||||
result = setupInstance2->GetInstallationPath(&installationPath);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
BSTR productPath;
|
||||
result = setupInstance2->GetProductPath(&productPath);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
auto& info = infos[count++];
|
||||
|
||||
char version[3];
|
||||
version[0] = (char)installationVersion[0];
|
||||
version[1] = (char)installationVersion[1];
|
||||
version[2] = 0;
|
||||
info.VersionMajor = atoi(version);
|
||||
|
||||
swprintf_s(info.ExecutablePath, MAX_PATH, L"%s\\%s", installationPath, productPath);
|
||||
|
||||
wchar_t progID[100];
|
||||
swprintf_s(progID, 100, L"VisualStudio.DTE.%d.0", info.VersionMajor);
|
||||
|
||||
CLSID clsid;
|
||||
CLSIDFromProgID(progID, &clsid);
|
||||
StringFromGUID2(clsid, info.CLSID, 40);
|
||||
|
||||
SysFreeString(productPath);
|
||||
}
|
||||
SysFreeString(installationPath);
|
||||
}
|
||||
SysFreeString(installationVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void VisualStudio::OpenConnection(ConnectionHandle& connection, const wchar_t* clsID, const wchar_t* solutionPath)
|
||||
{
|
||||
connection = new ConnectionInternal(clsID, solutionPath);
|
||||
}
|
||||
|
||||
void VisualStudio::CloseConnection(ConnectionHandle& connection)
|
||||
{
|
||||
if (connection)
|
||||
{
|
||||
delete connection;
|
||||
connection = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool VisualStudio::IsActive(const ConnectionHandle& connection)
|
||||
{
|
||||
// Check if already opened
|
||||
if (connection->IsValid())
|
||||
return true;
|
||||
|
||||
// Try to find active
|
||||
auto e = FindRunningInstance(connection);
|
||||
return connection->DTE != nullptr;
|
||||
}
|
||||
|
||||
VisualStudio::Result VisualStudio::OpenSolution(ConnectionHandle connection)
|
||||
{
|
||||
// Check if already opened
|
||||
if (connection->IsValid())
|
||||
return Result::Ok;
|
||||
|
||||
// Temporary data
|
||||
CleanupHelper helper;
|
||||
HRESULT result;
|
||||
|
||||
// Cache VS version CLSID
|
||||
result = CLSIDFromString(connection->ClsID, &connection->CLSID);
|
||||
CHECK_VS_RESULT("VisualStudio::CLSIDFromString");
|
||||
|
||||
// Get or open VS with solution
|
||||
auto e = FindRunningInstance(connection);
|
||||
if (e.Failed())
|
||||
return e;
|
||||
if (connection->DTE == nullptr)
|
||||
{
|
||||
e = OpenInstance(connection);
|
||||
if (e.Failed())
|
||||
return e;
|
||||
if (connection->DTE == nullptr)
|
||||
return "Cannot open Visual Studio";
|
||||
}
|
||||
|
||||
// Focus VS main window
|
||||
ComPtr<EnvDTE::Window> window;
|
||||
if (SUCCEEDED(connection->DTE->get_MainWindow(&window)))
|
||||
window->Activate();
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
VisualStudio::Result VisualStudio::OpenFile(ConnectionHandle connection, const wchar_t* path, unsigned int line)
|
||||
{
|
||||
// Ensure to have valid connection
|
||||
auto result = OpenSolution(connection);
|
||||
if (result.Failed())
|
||||
return result;
|
||||
|
||||
// Open file
|
||||
CleanupHelper helper;
|
||||
const LocalBSTR pathBstr(path);
|
||||
return OpenFile(connection, pathBstr.Str, line);
|
||||
}
|
||||
|
||||
VisualStudio::Result VisualStudio::AddFile(ConnectionHandle connection, const wchar_t* path, const wchar_t* localPath)
|
||||
{
|
||||
// Ensure to have valid connection
|
||||
auto result = OpenSolution(connection);
|
||||
if (result.Failed())
|
||||
return result;
|
||||
|
||||
// Add file
|
||||
CleanupHelper helper;
|
||||
LocalBSTR pathBstr(path);
|
||||
return AddFile(connection, pathBstr.Str, localPath);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
#include <string>
|
||||
|
||||
/// <summary>
|
||||
/// Contains various helper classes for interacting with a Visual Studio instance running on this machine.
|
||||
/// </summary>
|
||||
namespace VisualStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// Visual Studio connection operation result
|
||||
/// </summary>
|
||||
struct Result
|
||||
{
|
||||
static Result Ok;
|
||||
|
||||
std::string Message;
|
||||
|
||||
Result()
|
||||
{
|
||||
Message = "";
|
||||
}
|
||||
|
||||
Result(const char* msg)
|
||||
{
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
Result(const std::string& msg)
|
||||
{
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
bool Failed() const
|
||||
{
|
||||
return Message.size() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct InstanceInfo
|
||||
{
|
||||
wchar_t CLSID[40];
|
||||
wchar_t ExecutablePath[MAX_PATH];
|
||||
int VersionMajor;
|
||||
};
|
||||
|
||||
class ConnectionInternal;
|
||||
typedef ConnectionInternal* ConnectionHandle;
|
||||
|
||||
int GetVisualStudioVersions(InstanceInfo* infos, int infosCount);
|
||||
void OpenConnection(ConnectionHandle& connection, const wchar_t* clsID, const wchar_t* solutionPath);
|
||||
void CloseConnection(ConnectionHandle& connection);
|
||||
bool IsActive(const ConnectionHandle& connection);
|
||||
Result OpenSolution(ConnectionHandle connection);
|
||||
Result OpenFile(ConnectionHandle connection, const wchar_t* path, unsigned int line);
|
||||
Result AddFile(ConnectionHandle connection, const wchar_t* path, const wchar_t* localPath);
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio connection wrapper class
|
||||
/// </summary>
|
||||
class Connection
|
||||
{
|
||||
private:
|
||||
|
||||
ConnectionHandle Handle;
|
||||
|
||||
public:
|
||||
|
||||
Connection(const wchar_t* clsID, const wchar_t* solutionPath)
|
||||
{
|
||||
OpenConnection(Handle, clsID, solutionPath);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
CloseConnection(Handle);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool IsActive() const
|
||||
{
|
||||
return VisualStudio::IsActive(Handle);
|
||||
}
|
||||
|
||||
Result OpenSolution() const
|
||||
{
|
||||
return VisualStudio::OpenSolution(Handle);
|
||||
}
|
||||
|
||||
Result OpenFile(const wchar_t* path, unsigned int line) const
|
||||
{
|
||||
return VisualStudio::OpenFile(Handle, path, line);
|
||||
}
|
||||
|
||||
Result AddFile(const wchar_t* path, const wchar_t* localPath) const
|
||||
{
|
||||
return VisualStudio::AddFile(Handle, path, localPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
#include "VisualStudioEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
#include "VisualStudioConnection.h"
|
||||
|
||||
VisualStudioEditor::VisualStudioEditor(VisualStudioVersion version, const String& execPath, const String& CLSID)
|
||||
: _version(version)
|
||||
, _execPath(execPath)
|
||||
, _CLSID(CLSID)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case VisualStudioVersion::VS2008:
|
||||
_type = CodeEditorTypes::VS2008;
|
||||
break;
|
||||
case VisualStudioVersion::VS2010:
|
||||
_type = CodeEditorTypes::VS2010;
|
||||
break;
|
||||
case VisualStudioVersion::VS2012:
|
||||
_type = CodeEditorTypes::VS2012;
|
||||
break;
|
||||
case VisualStudioVersion::VS2013:
|
||||
_type = CodeEditorTypes::VS2013;
|
||||
break;
|
||||
case VisualStudioVersion::VS2015:
|
||||
_type = CodeEditorTypes::VS2015;
|
||||
break;
|
||||
case VisualStudioVersion::VS2017:
|
||||
_type = CodeEditorTypes::VS2017;
|
||||
break;
|
||||
case VisualStudioVersion::VS2019:
|
||||
_type = CodeEditorTypes::VS2019;
|
||||
break;
|
||||
default: CRASH;
|
||||
break;
|
||||
}
|
||||
_solutionPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln");
|
||||
}
|
||||
|
||||
void VisualStudioEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
String installDir;
|
||||
String clsID;
|
||||
String regVsRootNode;
|
||||
|
||||
// Select registry node for
|
||||
if (Platform::Is64BitPlatform())
|
||||
regVsRootNode = TEXT("SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\");
|
||||
else
|
||||
regVsRootNode = TEXT("SOFTWARE\\Microsoft\\VisualStudio\\");
|
||||
|
||||
VisualStudio::InstanceInfo infos[8];
|
||||
const int32 count = VisualStudio::GetVisualStudioVersions(infos, ARRAY_COUNT(infos));
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& info = infos[i];
|
||||
VisualStudioVersion version;
|
||||
switch (info.VersionMajor)
|
||||
{
|
||||
case 16:
|
||||
version = VisualStudioVersion::VS2019;
|
||||
break;
|
||||
case 15:
|
||||
version = VisualStudioVersion::VS2017;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
String executablePath(info.ExecutablePath);
|
||||
if (!FileSystem::FileExists(executablePath))
|
||||
continue;
|
||||
|
||||
// Create editor
|
||||
clsID = info.CLSID;
|
||||
auto editor = New<VisualStudioEditor>(version, executablePath, clsID);
|
||||
output->Add(editor);
|
||||
}
|
||||
|
||||
struct VersionData
|
||||
{
|
||||
VisualStudioVersion Version;
|
||||
const Char* RegistryKey;
|
||||
};
|
||||
|
||||
const VersionData versions[] =
|
||||
{
|
||||
// Order matters
|
||||
{ VisualStudioVersion::VS2015, TEXT("14.0"), },
|
||||
{ VisualStudioVersion::VS2013, TEXT("12.0"), },
|
||||
{ VisualStudioVersion::VS2012, TEXT("11.0"), },
|
||||
{ VisualStudioVersion::VS2012, TEXT("11.0"), },
|
||||
{ VisualStudioVersion::VS2008, TEXT("9.0") },
|
||||
};
|
||||
|
||||
for (int32 i = 0; i < ARRAY_COUNT(versions); i++)
|
||||
{
|
||||
auto registryKey = regVsRootNode + versions[i].RegistryKey;
|
||||
|
||||
// Read install directory
|
||||
if (Platform::ReadRegValue(HKEY_LOCAL_MACHINE, registryKey, TEXT("InstallDir"), &installDir) || installDir.IsEmpty())
|
||||
continue;
|
||||
|
||||
// Ensure that file exists
|
||||
String execPath = installDir + TEXT("devenv.exe");
|
||||
if (!FileSystem::FileExists(execPath))
|
||||
continue;
|
||||
|
||||
// Read version info id
|
||||
clsID.Clear();
|
||||
Platform::ReadRegValue(HKEY_LOCAL_MACHINE, registryKey, TEXT("ThisVersionDTECLSID"), &clsID);
|
||||
|
||||
// Create editor
|
||||
auto editor = New<VisualStudioEditor>(versions[i].Version, execPath, clsID);
|
||||
output->Add(editor);
|
||||
}
|
||||
}
|
||||
|
||||
CodeEditorTypes VisualStudioEditor::GetType() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
String VisualStudioEditor::GetName() const
|
||||
{
|
||||
return String(ToString(_version));
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject();
|
||||
}
|
||||
|
||||
// Open file
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const auto result = connection.OpenFile(*path, line);
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot open file \'{0}\':{1}. {2}.", path, line, String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject();
|
||||
}
|
||||
|
||||
// Open solution
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const auto result = connection.OpenSolution();
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot open solution. {0}", String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OnFileAdded(const String& path)
|
||||
{
|
||||
// TODO: finish dynamic files adding to the project - for now just regenerate it
|
||||
ScriptsBuilder::GenerateProject();
|
||||
return;
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Edit solution
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
if (connection.IsActive())
|
||||
{
|
||||
String tmp = path;
|
||||
tmp.Replace('/', '\\');
|
||||
String tmp2 = tmp.Substring(Globals::ProjectSourceFolder.Length() + 1);
|
||||
const auto result = connection.AddFile(*tmp, *tmp2);
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot add file to project. {0}", String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VisualStudioEditor::UseAsyncForOpen() const
|
||||
{
|
||||
// Need to generate project files if missing first
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
return true;
|
||||
|
||||
// Open in async only when no solution opened
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
return !connection.IsActive();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
#include "Engine/Core/Enums.h"
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft Visual Studio version types
|
||||
/// </summary>
|
||||
DECLARE_ENUM_7(VisualStudioVersion, VS2008, VS2010, VS2012, VS2013, VS2015, VS2017, VS2019);
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of code editor utility that is using Microsoft Visual Studio.
|
||||
/// </summary>
|
||||
class VisualStudioEditor : public CodeEditor
|
||||
{
|
||||
private:
|
||||
|
||||
VisualStudioVersion _version;
|
||||
CodeEditorTypes _type;
|
||||
String _execPath;
|
||||
String _CLSID;
|
||||
String _solutionPath;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VisualStudioEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="version">VS version</param>
|
||||
/// <param name="execPath">Executable file path</param>
|
||||
/// <param name="CLSID">CLSID of VS</param>
|
||||
VisualStudioEditor(VisualStudioVersion version, const String& execPath, const String& CLSID);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets version of Visual Studio
|
||||
/// </summary>
|
||||
/// <returns>VS version</returns>
|
||||
FORCE_INLINE VisualStudioVersion GetVersion() const
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Try to find installed Visual Studio instances. Adds them to the result list.
|
||||
/// </summary>
|
||||
/// <param name="output">The output editors.</param>
|
||||
static void FindEditors(Array<CodeEditor*>* output);
|
||||
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
void OnFileAdded(const String& path) override;
|
||||
bool UseAsyncForOpen() const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "VisualStudioCodeEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
|
||||
VisualStudioCodeEditor::VisualStudioCodeEditor(const String& execPath)
|
||||
: _execPath(execPath)
|
||||
, _workspacePath(Globals::ProjectFolder / Editor::Project->Name + TEXT(".code-workspace"))
|
||||
{
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
#if PLATFORM_WINDOWS
|
||||
String cmd;
|
||||
if (Platform::ReadRegValue(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Classes\\Applications\\Code.exe\\shell\\open\\command"), TEXT(""), &cmd) || cmd.IsEmpty())
|
||||
{
|
||||
if (Platform::ReadRegValue(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Classes\\Applications\\Code.exe\\shell\\open\\command"), TEXT(""), &cmd) || cmd.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
const String path = cmd.Substring(1, cmd.Length() - String(TEXT("\" \"%1\"")).Length() - 1);
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
output->Add(New<VisualStudioCodeEditor>(path));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CodeEditorTypes VisualStudioCodeEditor::GetType() const
|
||||
{
|
||||
return CodeEditorTypes::VSCode;
|
||||
}
|
||||
|
||||
String VisualStudioCodeEditor::GetName() const
|
||||
{
|
||||
return TEXT("Visual Studio Code");
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if missing
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / TEXT(".vscode/tasks.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vscode"));
|
||||
}
|
||||
|
||||
// Open file
|
||||
line = line > 0 ? line : 1;
|
||||
const String args = String::Format(TEXT("\"{0}\" -g \"{1}\":{2}"), _workspacePath, path, line);
|
||||
Platform::StartProcess(_execPath, args, StringView::Empty);
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / TEXT(".vscode/tasks.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vscode"));
|
||||
}
|
||||
|
||||
// Open solution
|
||||
const String args = String::Format(TEXT("\"{0}\""), _workspacePath);
|
||||
Platform::StartProcess(_execPath, args, StringView::Empty);
|
||||
}
|
||||
|
||||
bool VisualStudioCodeEditor::UseAsyncForOpen() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
41
Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
Normal file
41
Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of code editor utility that is using Microsoft Visual Studio Code.
|
||||
/// </summary>
|
||||
class VisualStudioCodeEditor : public CodeEditor
|
||||
{
|
||||
private:
|
||||
|
||||
String _execPath;
|
||||
String _workspacePath;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VisualStudioEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="execPath">Executable file path</param>
|
||||
VisualStudioCodeEditor(const String& execPath);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find installed Visual Studio Code instance. Adds them to the result list.
|
||||
/// </summary>
|
||||
/// <param name="output">The output editors.</param>
|
||||
static void FindEditors(Array<CodeEditor*>* output);
|
||||
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
bool UseAsyncForOpen() const override;
|
||||
};
|
||||
1546
Source/Editor/Scripting/ScriptType.cs
Normal file
1546
Source/Editor/Scripting/ScriptType.cs
Normal file
File diff suppressed because it is too large
Load Diff
662
Source/Editor/Scripting/ScriptsBuilder.cpp
Normal file
662
Source/Editor/Scripting/ScriptsBuilder.cpp
Normal file
@@ -0,0 +1,662 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ScriptsBuilder.h"
|
||||
#include "CodeEditor.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Managed/ManagedEditor.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringBuilder.h"
|
||||
#include "Engine/Debug/Exceptions/FileNotFoundException.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/FileSystemWatcher.h"
|
||||
#include "Engine/Threading/ThreadPool.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
enum class EventType
|
||||
{
|
||||
Unknown = -1,
|
||||
CompileBegin = 0,
|
||||
CompileStarted = 1,
|
||||
CompileEndGood = 2,
|
||||
CompileEndFailed = 3,
|
||||
ReloadCalled = 4,
|
||||
ReloadBegin = 5,
|
||||
Reload = 6,
|
||||
ReloadEnd = 7,
|
||||
};
|
||||
|
||||
struct EventData
|
||||
{
|
||||
EventType Type;
|
||||
};
|
||||
|
||||
namespace ScriptsBuilderImpl
|
||||
{
|
||||
CriticalSection _locker;
|
||||
bool _isInited = false;
|
||||
bool _isCompileRequested = false;
|
||||
bool _isCompileRunning = false;
|
||||
bool _wasProjectStructureChanged = false;
|
||||
bool _lastCompilationFailed = false;
|
||||
int32 _compilationsCount = 0;
|
||||
DateTime _lastSourceCodeEdited = 0;
|
||||
DateTime _lastCompileAction = 0;
|
||||
|
||||
Array<FileSystemWatcher*> SourceFoldersWatchers;
|
||||
|
||||
CriticalSection _compileEventsLocker;
|
||||
Array<EventData> _compileEvents;
|
||||
|
||||
MMethod* Internal_OnEvent = nullptr;
|
||||
MMethod* Internal_OnCompileEvent = nullptr;
|
||||
MMethod* Internal_OnCodeEditorEvent = nullptr;
|
||||
|
||||
void CallEvent(EventType type);
|
||||
void CallCompileEvent(EventData& data);
|
||||
void CallCodeEditorEvent(bool isEnd);
|
||||
|
||||
void sourceDirEvent(const String& path, FileSystemAction action);
|
||||
void onEditorAssemblyUnloading(MAssembly* assembly);
|
||||
void onScriptsReloadStart();
|
||||
void onScriptsReload();
|
||||
void onScriptsReloadEnd();
|
||||
|
||||
void GetClassName(const MString& fullname, MString& className);
|
||||
|
||||
void onCodeEditorAsyncOpenBegin()
|
||||
{
|
||||
CallCodeEditorEvent(false);
|
||||
}
|
||||
|
||||
void onCodeEditorAsyncOpenEnd()
|
||||
{
|
||||
CallCodeEditorEvent(true);
|
||||
}
|
||||
|
||||
bool compileGameScriptsAsyncInner();
|
||||
bool compileGameScriptsAsync();
|
||||
}
|
||||
|
||||
using namespace ScriptsBuilderImpl;
|
||||
|
||||
class ScriptsBuilderService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
ScriptsBuilderService()
|
||||
: EngineService(TEXT("Scripts Builder"))
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Update() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
ScriptsBuilderService ScriptsBuilderServiceInstance;
|
||||
|
||||
Delegate<bool> ScriptsBuilder::OnCompilationEnd;
|
||||
Action ScriptsBuilder::OnCompilationSuccess;
|
||||
Action ScriptsBuilder::OnCompilationFailed;
|
||||
|
||||
void ScriptsBuilderImpl::sourceDirEvent(const String& path, FileSystemAction action)
|
||||
{
|
||||
// Discard non-source files or generated files
|
||||
if (!path.EndsWith(TEXT(".cs")) &&
|
||||
!path.EndsWith(TEXT(".cpp")) &&
|
||||
!path.EndsWith(TEXT(".h")) ||
|
||||
path.EndsWith(TEXT(".Gen.cs")))
|
||||
return;
|
||||
|
||||
ScopeLock scopeLock(_locker);
|
||||
_lastSourceCodeEdited = DateTime::Now();
|
||||
}
|
||||
|
||||
int32 ScriptsBuilder::GetCompilationsCount()
|
||||
{
|
||||
Platform::MemoryBarrier();
|
||||
return _compilationsCount;
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::LastCompilationFailed()
|
||||
{
|
||||
return _lastCompilationFailed;
|
||||
}
|
||||
|
||||
void ScriptsBuilder::FilterNamespaceText(String& value)
|
||||
{
|
||||
value.Replace(TEXT(" "), TEXT(""), StringSearchCase::CaseSensitive);
|
||||
value.Replace(TEXT("."), TEXT(""), StringSearchCase::CaseSensitive);
|
||||
value.Replace(TEXT("-"), TEXT(""), StringSearchCase::CaseSensitive);
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::IsSourceDirty()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
return _lastSourceCodeEdited > _lastCompileAction;
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::IsSourceWorkspaceDirty()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
return _wasProjectStructureChanged;
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::IsSourceDirty(const TimeSpan& timeout)
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
return _lastSourceCodeEdited > (_lastCompileAction + timeout);
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::IsCompiling()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
return _isCompileRunning;
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::IsReady()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
return !IsSourceDirty() && !_isCompileRequested && !_isCompileRunning;
|
||||
}
|
||||
|
||||
void ScriptsBuilder::MarkWorkspaceDirty()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
_lastSourceCodeEdited = DateTime::Now();
|
||||
_wasProjectStructureChanged = true;
|
||||
}
|
||||
|
||||
void ScriptsBuilder::CheckForCompile()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
if (IsSourceDirty())
|
||||
Compile();
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::onScriptsReloadStart()
|
||||
{
|
||||
CallEvent(EventType::ReloadBegin);
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::onScriptsReload()
|
||||
{
|
||||
CallEvent(EventType::Reload);
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::onScriptsReloadEnd()
|
||||
{
|
||||
CallEvent(EventType::ReloadEnd);
|
||||
}
|
||||
|
||||
void ScriptsBuilder::Compile()
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
|
||||
// Request compile job
|
||||
_isCompileRequested = true;
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::RunBuildTool(const StringView& args)
|
||||
{
|
||||
const String buildToolPath = Globals::StartupFolder / TEXT("Binaries/Tools/Flax.Build.exe");
|
||||
if (!FileSystem::FileExists(buildToolPath))
|
||||
{
|
||||
Log::FileNotFoundException(buildToolPath).SetLevel(LogType::Fatal);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// Prepare build options
|
||||
StringBuilder cmdLine(args.Length() + buildToolPath.Length() + 30);
|
||||
cmdLine.Append(TEXT("\""));
|
||||
cmdLine.Append(buildToolPath);
|
||||
cmdLine.Append(TEXT("\" "));
|
||||
cmdLine.Append(args.Get(), args.Length());
|
||||
cmdLine.Append(TEXT('\0'));
|
||||
#else
|
||||
// Use mono to run the build tool
|
||||
const String monoPath = Globals::MonoPath / TEXT("bin/mono.exe");
|
||||
if (!FileSystem::FileExists(monoPath))
|
||||
{
|
||||
Log::FileNotFoundException(monoPath).SetLevel(LogType::Fatal);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare build options
|
||||
StringBuilder cmdLine(monoPath.Length() + args.Length() + buildToolPath.Length() + 30);
|
||||
cmdLine.Append(TEXT("\""));
|
||||
cmdLine.Append(monoPath);
|
||||
cmdLine.Append(TEXT("\" \""));
|
||||
cmdLine.Append(buildToolPath);
|
||||
cmdLine.Append(TEXT("\" "));
|
||||
cmdLine.Append(args.Get(), args.Length());
|
||||
cmdLine.Append(TEXT('\0'));
|
||||
|
||||
// TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance
|
||||
#endif
|
||||
|
||||
// Call build tool
|
||||
const int32 result = Platform::RunProcess(StringView(*cmdLine, cmdLine.Length()), StringView::Empty);
|
||||
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
|
||||
{
|
||||
String args(TEXT("-log -genproject "));
|
||||
args += customArgs;
|
||||
_wasProjectStructureChanged = false;
|
||||
return RunBuildTool(args);
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::GetClassName(const MString& fullname, MString& className)
|
||||
{
|
||||
const auto lastDotIndex = fullname.FindLast('.');
|
||||
if (lastDotIndex != -1)
|
||||
{
|
||||
//namespaceName = fullname.Substring(0, lastDotIndex);
|
||||
className = fullname.Substring(lastDotIndex + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
className = fullname;
|
||||
}
|
||||
}
|
||||
|
||||
MClass* ScriptsBuilder::FindScript(const StringView& scriptName)
|
||||
{
|
||||
const MString scriptNameStd = scriptName.ToStringAnsi();
|
||||
|
||||
const ScriptingTypeHandle scriptingType = Scripting::FindScriptingType(scriptNameStd);
|
||||
if (scriptingType)
|
||||
{
|
||||
MClass* mclass = scriptingType.GetType().ManagedClass;
|
||||
if (mclass)
|
||||
{
|
||||
return mclass;
|
||||
}
|
||||
}
|
||||
|
||||
// Check all assemblies (ignoring the typename namespace)
|
||||
auto& modules = BinaryModule::GetModules();
|
||||
MString className;
|
||||
GetClassName(scriptNameStd, className);
|
||||
MClass* scriptClass = Script::GetStaticClass();
|
||||
for (int32 j = 0; j < modules.Count(); j++)
|
||||
{
|
||||
auto managedModule = dynamic_cast<ManagedBinaryModule*>(modules[j]);
|
||||
if (!managedModule)
|
||||
continue;
|
||||
auto assembly = managedModule->Assembly;
|
||||
auto& classes = assembly->GetClasses();
|
||||
for (auto i = classes.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
MClass* mclass = i->Value;
|
||||
|
||||
// Managed scripts
|
||||
if (mclass->IsSubClassOf(scriptClass) && !mclass->IsStatic() && !mclass->IsAbstract() && !mclass->IsInterface())
|
||||
{
|
||||
MString mclassName;
|
||||
GetClassName(mclass->GetFullName(), mclassName);
|
||||
if (className == mclassName)
|
||||
{
|
||||
LOG(Info, "Found {0} type for type {1} (assembly {2})", mclass->ToString(), String(scriptName.Get(), scriptName.Length()), assembly->ToString());
|
||||
return mclass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG(Warning, "Failed to find script class of name {0}", String(scriptNameStd));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ScriptsBuilder::GetExistingEditors(int32* result, int32 count)
|
||||
{
|
||||
auto& editors = CodeEditingManager::GetEditors();
|
||||
for (int32 i = 0; i < editors.Count() && i < count; i++)
|
||||
{
|
||||
result[static_cast<int32>(editors[i]->GetType())] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptsBuilder::GetBinariesConfiguration(StringView& target, StringView& platform, StringView& architecture, StringView& configuration)
|
||||
{
|
||||
const Char *targetPtr, *platformPtr, *architecturePtr, *configurationPtr;
|
||||
GetBinariesConfiguration(targetPtr, platformPtr, architecturePtr, configurationPtr);
|
||||
target = targetPtr;
|
||||
platform = platformPtr;
|
||||
architecture = architecturePtr;
|
||||
configuration = configurationPtr;
|
||||
}
|
||||
|
||||
void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*& platform, const Char*& architecture, const Char*& configuration)
|
||||
{
|
||||
// Special case when opening engine project
|
||||
if (Editor::Project->ProjectFolderPath == Globals::StartupFolder)
|
||||
{
|
||||
target = platform = architecture = configuration = nullptr;
|
||||
return;
|
||||
}
|
||||
target = Editor::Project->EditorTarget.GetText();
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
platform = TEXT("Windows");
|
||||
#endif
|
||||
|
||||
#if PLATFORM_ARCH_X64
|
||||
architecture = TEXT("x64");
|
||||
#elif PLATFORM_ARCH_X86
|
||||
architecture = TEXT("x86");
|
||||
#endif
|
||||
|
||||
#if BUILD_DEBUG
|
||||
configuration = TEXT("Debug");
|
||||
#elif BUILD_DEVELOPMENT
|
||||
configuration = TEXT("Development");
|
||||
#elif BUILD_RELEASE
|
||||
configuration = TEXT("Release");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ScriptsBuilderImpl::compileGameScriptsAsyncInner()
|
||||
{
|
||||
LOG(Info, "Starting scripts compilation...");
|
||||
CallEvent(EventType::CompileStarted);
|
||||
|
||||
// Call compilation
|
||||
const Char *target, *platform, *architecture, *configuration;
|
||||
ScriptsBuilder::GetBinariesConfiguration(target, platform, architecture, configuration);
|
||||
if (!target)
|
||||
{
|
||||
LOG(Info, "Missing EditorTarget in project. Skipping compilation.");
|
||||
CallEvent(EventType::ReloadCalled);
|
||||
Scripting::Reload();
|
||||
return false;
|
||||
}
|
||||
auto args = String::Format(
|
||||
TEXT("-log -logfile= -build -mutex -buildtargets={0} -skiptargets=FlaxEditor -platform={1} -arch={2} -configuration={3}"),
|
||||
target, platform, architecture, configuration);
|
||||
if (Scripting::HasGameModulesLoaded())
|
||||
{
|
||||
// Add postfix to output binaries to prevent file locking collisions when doing hot-reload in Editor
|
||||
args += String::Format(TEXT(" -hotreload=\".HotReload.{0}\""), _compilationsCount - 1);
|
||||
}
|
||||
if (ScriptsBuilder::RunBuildTool(args))
|
||||
return true;
|
||||
|
||||
// Reload scripts
|
||||
CallEvent(EventType::ReloadCalled);
|
||||
Scripting::Reload();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::CallEvent(EventType type)
|
||||
{
|
||||
ScopeLock lock(_compileEventsLocker);
|
||||
|
||||
const int32 index = _compileEvents.Count();
|
||||
_compileEvents.AddDefault(1);
|
||||
auto& data = _compileEvents[index];
|
||||
data.Type = type;
|
||||
|
||||
// Flush events on a main tread
|
||||
if (IsInMainThread())
|
||||
{
|
||||
for (int32 i = 0; i < _compileEvents.Count(); i++)
|
||||
CallCompileEvent(_compileEvents[i]);
|
||||
_compileEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::CallCompileEvent(EventData& data)
|
||||
{
|
||||
ASSERT(IsInMainThread());
|
||||
|
||||
// Special case for a single events with no data
|
||||
if (data.Type != EventType::Unknown)
|
||||
{
|
||||
// Call C# event
|
||||
if (Internal_OnEvent == nullptr)
|
||||
{
|
||||
auto scriptsBuilderClass = ScriptsBuilder::GetStaticClass();
|
||||
if (scriptsBuilderClass)
|
||||
Internal_OnEvent = scriptsBuilderClass->GetMethod("Internal_OnEvent", 1);
|
||||
if (Internal_OnEvent == nullptr)
|
||||
{
|
||||
LOG(Fatal, "Invalid Editor assembly!");
|
||||
}
|
||||
}
|
||||
/*MonoObject* exception = nullptr;
|
||||
void* args[1];
|
||||
args[0] = &data.Type;
|
||||
Internal_OnEvent->Invoke(nullptr, args, &exception);
|
||||
if (exception)
|
||||
{
|
||||
DebugLog::LogException(exception);
|
||||
}*/
|
||||
|
||||
MainThreadManagedInvokeAction::ParamsBuilder params;
|
||||
params.AddParam(data.Type);
|
||||
MainThreadManagedInvokeAction::Invoke(Internal_OnEvent, params);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::CallCodeEditorEvent(bool isEnd)
|
||||
{
|
||||
if (Internal_OnCodeEditorEvent == nullptr)
|
||||
{
|
||||
auto scriptsBuilderClass = ScriptsBuilder::GetStaticClass();
|
||||
if (scriptsBuilderClass)
|
||||
Internal_OnCodeEditorEvent = scriptsBuilderClass->GetMethod("Internal_OnCodeEditorEvent", 1);
|
||||
ASSERT(Internal_OnCodeEditorEvent);
|
||||
}
|
||||
|
||||
MainThreadManagedInvokeAction::ParamsBuilder params;
|
||||
params.AddParam(isEnd);
|
||||
MainThreadManagedInvokeAction::Invoke(Internal_OnCodeEditorEvent, params);
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::onEditorAssemblyUnloading(MAssembly* assembly)
|
||||
{
|
||||
Internal_OnEvent = nullptr;
|
||||
Internal_OnCompileEvent = nullptr;
|
||||
Internal_OnCodeEditorEvent = nullptr;
|
||||
}
|
||||
|
||||
bool ScriptsBuilderImpl::compileGameScriptsAsync()
|
||||
{
|
||||
// Start
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
|
||||
_isCompileRequested = false;
|
||||
_lastCompileAction = DateTime::Now();
|
||||
_compilationsCount++;
|
||||
_isCompileRunning = true;
|
||||
|
||||
ScriptsBuilderServiceInstance.Init();
|
||||
|
||||
CallEvent(EventType::CompileBegin);
|
||||
}
|
||||
|
||||
// Do work
|
||||
const bool success = !compileGameScriptsAsyncInner();
|
||||
|
||||
// End
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
|
||||
_lastCompilationFailed = !success;
|
||||
|
||||
ScriptsBuilder::OnCompilationEnd(success);
|
||||
if (success)
|
||||
ScriptsBuilder::OnCompilationSuccess();
|
||||
else
|
||||
ScriptsBuilder::OnCompilationFailed();
|
||||
if (success)
|
||||
CallEvent(EventType::CompileEndGood);
|
||||
else
|
||||
CallEvent(EventType::CompileEndFailed);
|
||||
_isCompileRunning = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScriptsBuilderService::Init()
|
||||
{
|
||||
// Check flag
|
||||
if (_isInited)
|
||||
return false;
|
||||
_isInited = true;
|
||||
|
||||
// Link for Editor assembly unload event to clear cached Internal_OnCompilationEnd to prevent errors
|
||||
auto editorAssembly = GetBinaryModuleFlaxEngine()->Assembly;
|
||||
editorAssembly->Unloading.Bind(onEditorAssemblyUnloading);
|
||||
|
||||
// Listen to scripts reloading events and forward them to c#
|
||||
Level::ScriptsReloadStart.Bind(onScriptsReloadStart);
|
||||
Level::ScriptsReload.Bind(onScriptsReload);
|
||||
Level::ScriptsReloadEnd.Bind(onScriptsReloadEnd);
|
||||
|
||||
// Listen to code editors manager events
|
||||
CodeEditingManager::AsyncOpenBegin.Bind(onCodeEditorAsyncOpenBegin);
|
||||
CodeEditingManager::AsyncOpenEnd.Bind(onCodeEditorAsyncOpenEnd);
|
||||
|
||||
// Create source folder watcher to handle scripts modification events (create/delete scripts events are handled by the editor itself)
|
||||
auto project = Editor::Project;
|
||||
HashSet<ProjectInfo*> projects;
|
||||
project->GetAllProjects(projects);
|
||||
for (auto e : projects)
|
||||
{
|
||||
ProjectInfo* project = e.Item;
|
||||
if (project->Name == TEXT("Flax"))
|
||||
continue;
|
||||
auto watcher = New<FileSystemWatcher>(project->ProjectFolderPath / TEXT("Source"), true);
|
||||
watcher->OnEvent.Bind(sourceDirEvent);
|
||||
SourceFoldersWatchers.Add(watcher);
|
||||
}
|
||||
|
||||
// Verify project
|
||||
if (project->EditorTarget.IsEmpty() || project->GameTarget.IsEmpty())
|
||||
{
|
||||
const String& name = project->Name;
|
||||
String codeName;
|
||||
for (int32 i = 0; i < name.Length(); i++)
|
||||
{
|
||||
Char c = name[i];
|
||||
if (StringUtils::IsAlnum(c) && c != ' ' && c != '.')
|
||||
codeName += c;
|
||||
}
|
||||
project->GameTarget = codeName + TEXT("Target");
|
||||
project->EditorTarget = codeName + TEXT("EditorTarget");
|
||||
LOG(Warning, "Missing EditorTarget property in opened project, using deducted target name {0}", Editor::Project->EditorTarget);
|
||||
}
|
||||
|
||||
// Remove any reaming files from previous Editor run hot-reloads
|
||||
const Char *target, *platform, *architecture, *configuration;
|
||||
ScriptsBuilder::GetBinariesConfiguration(target, platform, architecture, configuration);
|
||||
if (target)
|
||||
{
|
||||
const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration;
|
||||
Array<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, targetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
|
||||
if (files.HasItems())
|
||||
LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count());
|
||||
for (auto& file : files)
|
||||
{
|
||||
FileSystem::DeleteFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
bool forceRecompile = false;
|
||||
|
||||
// Check last Editor version that was using a project is different from current
|
||||
if (Editor::IsOldProjectOpened)
|
||||
forceRecompile = true;
|
||||
|
||||
// Check if need to force recompile game scripts
|
||||
if (forceRecompile)
|
||||
{
|
||||
LOG(Warning, "Forcing scripts recompilation");
|
||||
FileSystem::DeleteDirectory(Globals::ProjectCacheFolder / TEXT("Intermediate"));
|
||||
ScriptsBuilder::Compile();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScriptsBuilderService::Update()
|
||||
{
|
||||
// Send compilation events
|
||||
{
|
||||
ScopeLock scopeLock(_compileEventsLocker);
|
||||
|
||||
for (int32 i = 0; i < _compileEvents.Count(); i++)
|
||||
CallCompileEvent(_compileEvents[i]);
|
||||
_compileEvents.Clear();
|
||||
}
|
||||
|
||||
// Check if compile code (if has been edited)
|
||||
const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50);
|
||||
auto mainWindow = Engine::MainWindow;
|
||||
if (ScriptsBuilder::IsSourceDirty(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused())
|
||||
{
|
||||
// Check if auto reload is enabled
|
||||
if (Editor::Managed->CanAutoReloadScripts())
|
||||
ScriptsBuilder::Compile();
|
||||
}
|
||||
|
||||
ScopeLock scopeLock(_locker);
|
||||
|
||||
// Check if compilation has been requested (some time ago since we want to batch calls to reduce amount of compilations)
|
||||
if (_isCompileRequested)
|
||||
{
|
||||
// Check if compilation isn't running
|
||||
if (!_isCompileRunning)
|
||||
{
|
||||
// Check if editor state can perform scripts reloading
|
||||
if (Editor::Managed->CanReloadScripts())
|
||||
{
|
||||
// Call compilation (and switch flags)
|
||||
_isCompileRequested = false;
|
||||
_isCompileRunning = true;
|
||||
Function<bool()> action(compileGameScriptsAsync);
|
||||
Task::StartNew(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptsBuilderService::Dispose()
|
||||
{
|
||||
// Don't exit while scripts compilation is still running
|
||||
if (ScriptsBuilder::IsCompiling())
|
||||
{
|
||||
LOG(Warning, "Scripts compilation is running, waiting for the end...");
|
||||
int32 timeOutMilliseconds = 5000;
|
||||
while (ScriptsBuilder::IsCompiling() && timeOutMilliseconds > 0)
|
||||
{
|
||||
const int sleepTimeMilliseconds = 50;
|
||||
timeOutMilliseconds -= sleepTimeMilliseconds;
|
||||
Platform::Sleep(sleepTimeMilliseconds);
|
||||
}
|
||||
LOG(Warning, "Scripts compilation wait ended");
|
||||
}
|
||||
|
||||
SourceFoldersWatchers.ClearDelete();
|
||||
}
|
||||
132
Source/Editor/Scripting/ScriptsBuilder.cs
Normal file
132
Source/Editor/Scripting/ScriptsBuilder.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
partial class ScriptsBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Compilation end event delegate.
|
||||
/// </summary>
|
||||
/// <param name="success">False if compilation has failed, otherwise true.</param>
|
||||
public delegate void CompilationEndDelegate(bool success);
|
||||
|
||||
/// <summary>
|
||||
/// Compilation message events delegate.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="file">The target file.</param>
|
||||
/// <param name="line">The target line.</param>
|
||||
public delegate void CompilationMessageDelegate(string message, string file, int line);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when compilation ends.
|
||||
/// </summary>
|
||||
public static event CompilationEndDelegate CompilationEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when compilation success.
|
||||
/// </summary>
|
||||
public static event Action CompilationSuccess;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when compilation failed.
|
||||
/// </summary>
|
||||
public static event Action CompilationFailed;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when compilation begins.
|
||||
/// </summary>
|
||||
public static event Action CompilationBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when compilation just started.
|
||||
/// </summary>
|
||||
public static event Action CompilationStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when user scripts reload action is called.
|
||||
/// </summary>
|
||||
public static event Action ScriptsReloadCalled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when user scripts reload starts.
|
||||
/// User objects should be removed at this point to reduce leaks and issues. Game scripts and game editor scripts assemblies will be reloaded.
|
||||
/// </summary>
|
||||
public static event Action ScriptsReloadBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when user scripts reload is performed (just before the actual reload, scenes are serialized and unloaded). All user objects should be cleanup.
|
||||
/// </summary>
|
||||
public static event Action ScriptsReload;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when user scripts reload ends.
|
||||
/// </summary>
|
||||
public static event Action ScriptsReloadEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when code editor starts asynchronous open a file or a solution.
|
||||
/// </summary>
|
||||
public static event Action CodeEditorAsyncOpenBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when code editor ends asynchronous open a file or a solution.
|
||||
/// </summary>
|
||||
public static event Action CodeEditorAsyncOpenEnd;
|
||||
|
||||
internal enum EventType
|
||||
{
|
||||
CompileBegin = 0,
|
||||
CompileStarted = 1,
|
||||
CompileEndGood = 2,
|
||||
CompileEndFailed = 3,
|
||||
ReloadCalled = 4,
|
||||
ReloadBegin = 5,
|
||||
Reload = 6,
|
||||
ReloadEnd = 7,
|
||||
}
|
||||
|
||||
internal static void Internal_OnEvent(EventType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EventType.CompileBegin:
|
||||
CompilationBegin?.Invoke();
|
||||
break;
|
||||
case EventType.CompileStarted:
|
||||
CompilationStarted?.Invoke();
|
||||
break;
|
||||
case EventType.CompileEndGood:
|
||||
CompilationEnd?.Invoke(true);
|
||||
CompilationSuccess?.Invoke();
|
||||
break;
|
||||
case EventType.CompileEndFailed:
|
||||
CompilationEnd?.Invoke(false);
|
||||
CompilationFailed?.Invoke();
|
||||
break;
|
||||
case EventType.ReloadCalled:
|
||||
ScriptsReloadCalled?.Invoke();
|
||||
break;
|
||||
case EventType.ReloadBegin:
|
||||
ScriptsReloadBegin?.Invoke();
|
||||
break;
|
||||
case EventType.Reload:
|
||||
ScriptsReload?.Invoke();
|
||||
break;
|
||||
case EventType.ReloadEnd:
|
||||
ScriptsReloadEnd?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Internal_OnCodeEditorEvent(bool isEnd)
|
||||
{
|
||||
if (isEnd)
|
||||
CodeEditorAsyncOpenEnd?.Invoke();
|
||||
else
|
||||
CodeEditorAsyncOpenBegin?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
131
Source/Editor/Scripting/ScriptsBuilder.h
Normal file
131
Source/Editor/Scripting/ScriptsBuilder.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Game scrips building service. Compiles user C# scripts into binary assemblies. Exposes many events used to track scripts compilation and reloading.
|
||||
/// </summary>
|
||||
API_CLASS(Static, Namespace="FlaxEditor") class ScriptsBuilder
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScriptsBuilder);
|
||||
public:
|
||||
|
||||
typedef Delegate<const String&, const String&, int32> CompileMsgDelegate;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Action called on compilation end, bool param is true if success, otherwise false
|
||||
/// </summary>
|
||||
static Delegate<bool> OnCompilationEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Action called when compilation success
|
||||
/// </summary>
|
||||
static Action OnCompilationSuccess;
|
||||
|
||||
/// <summary>
|
||||
/// Action called when compilation fails
|
||||
/// </summary>
|
||||
static Action OnCompilationFailed;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets amount of source code compile actions since Editor startup.
|
||||
/// </summary>
|
||||
API_PROPERTY() static int32 GetCompilationsCount();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if last scripting building failed due to errors.
|
||||
/// </summary>
|
||||
/// <returns>True if last compilation result was a fail, otherwise false.</returns>
|
||||
API_PROPERTY() static bool LastCompilationFailed();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if source code has been edited.
|
||||
/// </summary>
|
||||
/// <returns>True if source code is dirty, otherwise false.</returns>
|
||||
API_PROPERTY() static bool IsSourceDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if source code workspace has been edited (source file moved or deleted).
|
||||
/// </summary>
|
||||
/// <returns>True if source code workspace is dirty, otherwise false.</returns>
|
||||
API_PROPERTY() static bool IsSourceWorkspaceDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if source code has been edited and is dirty for given amount of time
|
||||
/// </summary>
|
||||
/// <param name="timeout">Time to use for checking.</param>
|
||||
/// <returns>True if source code is dirty, otherwise false.</returns>
|
||||
static bool IsSourceDirty(const TimeSpan& timeout);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if scripts are being now compiled/reloaded.
|
||||
/// </summary>
|
||||
/// <returns>True if scripts are being compiled/reloaded now.</returns>
|
||||
API_PROPERTY() static bool IsCompiling();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if source code has been compiled and assemblies are ready to load.
|
||||
/// </summary>
|
||||
/// <returns>True if assemblies are ready to load.</returns>
|
||||
API_PROPERTY() static bool IsReady();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that scripting directory has been modified so scripts need to be rebuild.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void MarkWorkspaceDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if need to compile source code. If so calls compilation.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void CheckForCompile();
|
||||
|
||||
/// <summary>
|
||||
/// Requests project source code compilation.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void Compile();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the Flax.Build tool in the current project workspace and waits for the process end (blocking). Prints the build tool output to the log. Can be invoked from any thread.
|
||||
/// </summary>
|
||||
/// <param name="args">The Flax.Build tool invocation arguments.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() static bool RunBuildTool(const StringView& args);
|
||||
|
||||
/// <summary>
|
||||
/// Generates the project files.
|
||||
/// </summary>
|
||||
/// <param name="customArgs">The additional Flax.Build tool invocation arguments.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() static bool GenerateProject(const StringView& customArgs = StringView::Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a script type with the given name.
|
||||
/// </summary>
|
||||
/// <param name="scriptName">The script full name.</param>
|
||||
/// <returns>Found script type or null if missing or invalid name.</returns>
|
||||
API_FUNCTION() static MClass* FindScript(const StringView& scriptName);
|
||||
|
||||
// Gets the list of existing in-build code editors.
|
||||
API_FUNCTION() static void GetExistingEditors(int32* result, int32 count);
|
||||
|
||||
// Gets the options for the game scripts to use for the Editor.
|
||||
API_FUNCTION() static void GetBinariesConfiguration(API_PARAM(Out) StringView& target, API_PARAM(Out) StringView& platform, API_PARAM(Out) StringView& architecture, API_PARAM(Out) StringView& configuration);
|
||||
static void GetBinariesConfiguration(const Char*& target, const Char*& platform, const Char*& architecture, const Char*& configuration);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Filters the project namespace text value to be valid (remove whitespace characters and other invalid ones).
|
||||
/// </summary>
|
||||
/// <param name="value">The in/out value.</param>
|
||||
static void FilterNamespaceText(String& value);
|
||||
};
|
||||
290
Source/Editor/Scripting/TypeUtils.cs
Normal file
290
Source/Editor/Scripting/TypeUtils.cs
Normal file
@@ -0,0 +1,290 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor scripting utilities and helper functions.
|
||||
/// </summary>
|
||||
public static class TypeUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom list of scripting types containers. Can be used by custom scripting languages to provide types info for the editor.
|
||||
/// </summary>
|
||||
public static List<IScriptTypesContainer> CustomTypes = new List<IScriptTypesContainer>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script type of the object.
|
||||
/// </summary>
|
||||
/// <param name="o">The object.</param>
|
||||
/// <returns>The scripting type of that object.</returns>
|
||||
public static ScriptType GetObjectType(object o)
|
||||
{
|
||||
if (o is FlaxEngine.Object flaxObject && FlaxEngine.Object.GetUnmanagedPtr(flaxObject) != IntPtr.Zero)
|
||||
{
|
||||
var type = flaxObject.GetType();
|
||||
var typeName = flaxObject.TypeName;
|
||||
return type.FullName != typeName ? GetType(typeName) : new ScriptType(type);
|
||||
}
|
||||
return o != null ? new ScriptType(o.GetType()) : ScriptType.Null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value for the given type (can be value type or reference type).
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>The created instance.</returns>
|
||||
public static object GetDefaultValue(ScriptType type)
|
||||
{
|
||||
if (type.Type == typeof(string))
|
||||
return string.Empty;
|
||||
if (type.Type == typeof(Color))
|
||||
return Color.White;
|
||||
if (type.Type == typeof(Quaternion))
|
||||
return Quaternion.Identity;
|
||||
if (type.Type == typeof(Transform))
|
||||
return Transform.Identity;
|
||||
if (type.Type == typeof(Matrix))
|
||||
return Matrix.Identity;
|
||||
if (type.Type == typeof(BoundingBox))
|
||||
return BoundingBox.Zero;
|
||||
if (type.Type == typeof(Rectangle))
|
||||
return Rectangle.Empty;
|
||||
if (type.Type == typeof(ChannelMask))
|
||||
return ChannelMask.Red;
|
||||
if (type.Type == typeof(MaterialSceneTextures))
|
||||
return MaterialSceneTextures.BaseColor;
|
||||
if (type.IsValueType)
|
||||
return type.CreateInstance();
|
||||
if (new ScriptType(typeof(object)).IsAssignableFrom(type))
|
||||
return null;
|
||||
if (type.CanCreateInstance)
|
||||
return type.CreateInstance();
|
||||
throw new NotSupportedException("Cannot create default value for type " + type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the derived types from the given base type (excluding that type) within the given assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The target assembly to check its types.</param>
|
||||
/// <param name="baseType">The base type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
public static void GetDerivedTypes(Assembly assembly, ScriptType baseType, List<ScriptType> result)
|
||||
{
|
||||
GetDerivedTypes(assembly, baseType, result, type => true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the derived types from the given base type (excluding that type) within all the loaded assemblies.
|
||||
/// </summary>
|
||||
/// <param name="baseType">The base type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
public static void GetDerivedTypes(ScriptType baseType, List<ScriptType> result)
|
||||
{
|
||||
GetDerivedTypes(baseType, result, type => true, assembly => true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the derived types from the given base type (excluding that type) within the given assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The target assembly to check its types.</param>
|
||||
/// <param name="baseType">The base type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
/// <param name="checkFunc">Additional callback used to check if the given type is valid. Returns true if add type, otherwise false.</param>
|
||||
public static void GetDerivedTypes(Assembly assembly, ScriptType baseType, List<ScriptType> result, Func<ScriptType, bool> checkFunc)
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
for (int i = 0; i < types.Length; i++)
|
||||
{
|
||||
var t = new ScriptType(types[i]);
|
||||
if (baseType.IsAssignableFrom(t) && t != baseType && checkFunc(t))
|
||||
result.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the derived types from the given base type (excluding that type) within all the loaded assemblies.
|
||||
/// </summary>
|
||||
/// <param name="baseType">The base type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
/// <param name="checkFunc">Additional callback used to check if the given type is valid. Returns true if add type, otherwise false.</param>
|
||||
public static void GetDerivedTypes(ScriptType baseType, List<ScriptType> result, Func<ScriptType, bool> checkFunc)
|
||||
{
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
GetDerivedTypes(assemblies[i], baseType, result, checkFunc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the derived types from the given base type (excluding that type) within all the loaded assemblies.
|
||||
/// </summary>
|
||||
/// <param name="baseType">The base type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
/// <param name="checkFunc">Additional callback used to check if the given type is valid. Returns true if add type, otherwise false.</param>
|
||||
/// <param name="checkAssembly">Additional callback used to check if the given assembly is valid. Returns true if search for types in the given assembly, otherwise false.</param>
|
||||
public static void GetDerivedTypes(ScriptType baseType, List<ScriptType> result, Func<ScriptType, bool> checkFunc, Func<Assembly, bool> checkAssembly)
|
||||
{
|
||||
// C#/C++ types
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
if (checkAssembly(assemblies[i]))
|
||||
GetDerivedTypes(assemblies[i], baseType, result, checkFunc);
|
||||
}
|
||||
|
||||
// Custom types
|
||||
foreach (var customTypesInfo in CustomTypes)
|
||||
customTypesInfo.GetDerivedTypes(baseType, result, checkFunc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the types that have the given attribute defined within the given assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The target assembly to check its types.</param>
|
||||
/// <param name="attributeType">The attribute type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
/// <param name="checkFunc">Additional callback used to check if the given type is valid. Returns true if add type, otherwise false.</param>
|
||||
public static void GetTypesWithAttributeDefined(Assembly assembly, Type attributeType, List<ScriptType> result, Func<ScriptType, bool> checkFunc)
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
for (int i = 0; i < types.Length; i++)
|
||||
{
|
||||
var t = new ScriptType(types[i]);
|
||||
if (Attribute.IsDefined(t.Type, attributeType, false) && checkFunc(t))
|
||||
result.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the types that have the given attribute defined within all the loaded assemblies.
|
||||
/// </summary>
|
||||
/// <param name="attributeType">The attribute type.</param>
|
||||
/// <param name="result">The result collection. Elements will be added to it. Clear it before usage.</param>
|
||||
/// <param name="checkFunc">Additional callback used to check if the given type is valid. Returns true if add type, otherwise false.</param>
|
||||
/// <param name="checkAssembly">Additional callback used to check if the given assembly is valid. Returns true if search for types in the given assembly, otherwise false.</param>
|
||||
public static void GetTypesWithAttributeDefined(Type attributeType, List<ScriptType> result, Func<ScriptType, bool> checkFunc, Func<Assembly, bool> checkAssembly)
|
||||
{
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
if (checkAssembly(assemblies[i]))
|
||||
GetTypesWithAttributeDefined(assemblies[i], attributeType, result, checkFunc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the object type from the given full typename. Searches in-build Flax Engine/Editor assemblies and game assemblies.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The full name of the type.</param>
|
||||
/// <returns>The type or null if failed.</returns>
|
||||
public static Type GetManagedType(string typeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeName))
|
||||
return null;
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
var assembly = assemblies[i];
|
||||
if (assembly != null)
|
||||
{
|
||||
var type = assembly.GetType(typeName);
|
||||
if (type != null)
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the object type from the given full typename. Searches in-build Flax Engine/Editor assemblies and game assemblies.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The full name of the type.</param>
|
||||
/// <returns>The type or null if failed.</returns>
|
||||
public static ScriptType GetType(string typeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeName))
|
||||
return ScriptType.Null;
|
||||
|
||||
// C#/C++ types
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
var assembly = assemblies[i];
|
||||
if (assembly != null)
|
||||
{
|
||||
var type = assembly.GetType(typeName);
|
||||
if (type != null)
|
||||
{
|
||||
return new ScriptType(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom types
|
||||
foreach (var customTypesInfo in CustomTypes)
|
||||
{
|
||||
var type = customTypesInfo.GetType(typeName);
|
||||
if (type)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return ScriptType.Null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to create object instance of the given full typename. Searches in-build Flax Engine/Editor assemblies and game assemblies.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The full name of the type.</param>
|
||||
/// <returns>The created object or null if failed.</returns>
|
||||
public static object CreateInstance(string typeName)
|
||||
{
|
||||
var type = GetType(typeName);
|
||||
if (type)
|
||||
{
|
||||
object obj = null;
|
||||
try
|
||||
{
|
||||
return obj = type.CreateInstance();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input type represents a structure (value type but not enum nor primitive type).
|
||||
/// </summary>
|
||||
/// <param name="type">The input type of the object to check.</param>
|
||||
/// <returns>Returns true if the input type represents a structure (value type but not enum nor primitive type).</returns>
|
||||
public static bool IsStructure(this Type type)
|
||||
{
|
||||
return type.IsValueType && !type.IsEnum && !type.IsPrimitive;
|
||||
}
|
||||
|
||||
internal static bool IsDelegate(Type type)
|
||||
{
|
||||
return typeof(MulticastDelegate).IsAssignableFrom(type.BaseType);
|
||||
}
|
||||
|
||||
internal static Type GetType(ScriptType type)
|
||||
{
|
||||
while (type.Type == null)
|
||||
type = type.BaseType;
|
||||
return type.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Source/Editor/Scripting/Types.h
Normal file
11
Source/Editor/Scripting/Types.h
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
|
||||
struct CodeLocation
|
||||
{
|
||||
String File;
|
||||
uint32 Line;
|
||||
};
|
||||
Reference in New Issue
Block a user