C# Entity Scrips now working(OnInit, update, destroy) [ Broke reloading]
- added UI button to generate a .sln in the games folder - added UI for adding new component and drag n drop .cs file - added modular Core API module system
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -857,3 +857,8 @@ bin-int
|
||||
imgui.ini
|
||||
.vscode/launch.json
|
||||
.directory
|
||||
Nuake/dependencies/glad/bin-obj
|
||||
Nuake/dependencies/glad/glad.vcxproj
|
||||
Nuake/dependencies/glad/glad.vcxproj.filters
|
||||
*.vcxproj
|
||||
*.csproj
|
||||
|
||||
107
Editor/src/ComponentsPanel/NetScriptPanel.cpp
Normal file
107
Editor/src/ComponentsPanel/NetScriptPanel.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "NetScriptPanel.h"
|
||||
#include "../Windows/FileSystemUI.h"
|
||||
#include <src/Scene/Components/NetScriptComponent.h>
|
||||
#include <src/Core/FileSystem.h>
|
||||
|
||||
void NetScriptPanel::Draw(Nuake::Entity entity)
|
||||
{
|
||||
if (!entity.HasComponent<Nuake::NetScriptComponent>())
|
||||
return;
|
||||
|
||||
auto& component = entity.GetComponent<Nuake::NetScriptComponent>();
|
||||
BeginComponentTable(.NETSCRIPT, Nuake::NetScriptComponent);
|
||||
{
|
||||
{
|
||||
ImGui::Text("Script");
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
std::string path = component.ScriptPath;
|
||||
ImGui::Button(path.empty() ? "Create New" : component.ScriptPath.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0));
|
||||
if (ImGui::BeginDragDropTarget())
|
||||
{
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("_NetScript"))
|
||||
{
|
||||
char* file = (char*)payload->Data;
|
||||
|
||||
std::string fullPath = std::string(file, 512);
|
||||
path = Nuake::FileSystem::AbsoluteToRelative(std::move(fullPath));
|
||||
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
component.ScriptPath = path;
|
||||
|
||||
// Double click on file
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0))
|
||||
{
|
||||
if (!component.ScriptPath.empty())
|
||||
{
|
||||
Nuake::OS::OpenIn(Nuake::FileSystem::Root + "component.ScriptPath");
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Turn into command (Undo/Redo)
|
||||
std::string pathCreation = Nuake::FileDialog::SaveFile("*.cs");
|
||||
|
||||
if (!pathCreation.empty())
|
||||
{
|
||||
if (!Nuake::String::EndsWith(pathCreation, ".cs"))
|
||||
{
|
||||
pathCreation += ".cs";
|
||||
}
|
||||
|
||||
std::string fileName = Nuake::String::ToUpper(Nuake::FileSystem::GetFileNameFromPath(pathCreation));
|
||||
fileName = Nuake::String::RemoveWhiteSpace(fileName);
|
||||
|
||||
if (!Nuake::String::IsDigit(fileName[0]))
|
||||
{
|
||||
Nuake::FileSystem::BeginWriteFile(pathCreation);
|
||||
Nuake::FileSystem::WriteLine(NET_TEMPLATE_SCRIPT_FIRST + fileName + NET_TEMPLATE_SCRIPT_SECOND);
|
||||
Nuake::FileSystem::EndWriteFile();
|
||||
|
||||
path = Nuake::FileSystem::AbsoluteToRelative(pathCreation);
|
||||
Nuake::FileSystem::Scan();
|
||||
Nuake::FileSystemUI::m_CurrentDirectory = Nuake::FileSystem::RootDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
Nuake::Logger::Log("Cannot create script files that starts with a number.", "fileSystem", Nuake::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ComponentTableReset(component.ScriptPath, "");
|
||||
}
|
||||
//ImGui::TableNextColumn();
|
||||
//{
|
||||
// ImGui::Text("Module");
|
||||
// ImGui::TableNextColumn();
|
||||
//
|
||||
// // Here we create a dropdown for every modules
|
||||
// auto& wrenScript = component.mWrenScript;
|
||||
// if (wrenScript)
|
||||
// {
|
||||
// auto modules = wrenScript->GetModules();
|
||||
//
|
||||
// std::vector<const char*> modulesC;
|
||||
//
|
||||
// for (auto& m : modules)
|
||||
// {
|
||||
// modulesC.push_back(m.c_str());
|
||||
// }
|
||||
// static int currentModule = (int)component.mModule;
|
||||
// ImGui::Combo("##WrenModule", ¤tModule, &modulesC[0], modules.size());
|
||||
// component.mModule = currentModule;
|
||||
// }
|
||||
//
|
||||
// ImGui::TableNextColumn();
|
||||
// //ComponentTableReset(component.Class, "");
|
||||
//}
|
||||
}
|
||||
EndComponentTable();
|
||||
}
|
||||
45
Editor/src/ComponentsPanel/NetScriptPanel.h
Normal file
45
Editor/src/ComponentsPanel/NetScriptPanel.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "ComponentPanel.h"
|
||||
|
||||
|
||||
const std::string NET_TEMPLATE_SCRIPT_FIRST = R"(using Nuake.Net;
|
||||
|
||||
namespace NuakeShowcase
|
||||
{
|
||||
class )";
|
||||
|
||||
const std::string NET_TEMPLATE_SCRIPT_SECOND = R"( : Entity
|
||||
{
|
||||
public override void OnInit()
|
||||
{
|
||||
// Called once at the start of the game
|
||||
}
|
||||
|
||||
|
||||
public override void OnUpdate(float dt)
|
||||
{
|
||||
// Called every frame
|
||||
}
|
||||
|
||||
public override void OnFixedUpdate(float dt)
|
||||
{
|
||||
// Called every fixed update
|
||||
}
|
||||
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Called at the end of the game fixed update
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
class NetScriptPanel : ComponentPanel {
|
||||
|
||||
public:
|
||||
NetScriptPanel() {}
|
||||
|
||||
void Draw(Nuake::Entity entity) override;
|
||||
};
|
||||
@@ -52,6 +52,7 @@
|
||||
#include <src/UI/ImUI.h>
|
||||
|
||||
#include <src/Resource/StaticResources.h>
|
||||
#include <src/Scripting/ScriptingEngineNet.h>
|
||||
|
||||
namespace Nuake {
|
||||
|
||||
@@ -2009,6 +2010,16 @@ namespace Nuake {
|
||||
if (ImGui::MenuItem("Duplicate selected", NULL)) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu(".Net"))
|
||||
{
|
||||
if (ImGui::MenuItem("Generate Solution", NULL))
|
||||
{
|
||||
Nuake::ScriptingEngineNet::Get().GenerateSolution(FileSystem::Root, Engine::GetProject()->Name);
|
||||
Nuake::Logger::Log("Generated Solution.");
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Debug"))
|
||||
{
|
||||
if (ImGui::MenuItem("Show ImGui demo", NULL, m_ShowImGuiDemo)) m_ShowImGuiDemo = !m_ShowImGuiDemo;
|
||||
|
||||
@@ -112,6 +112,8 @@ void EditorSelectionPanel::DrawEntity(Nuake::Entity entity)
|
||||
mTransformPanel.Draw(entity);
|
||||
mLightPanel.Draw(entity);
|
||||
mScriptPanel.Draw(entity);
|
||||
mNetScriptPanel.Draw(entity);
|
||||
mAudioEmitterPanel.Draw(entity);
|
||||
mParticleEmitterPanel.Draw(entity);
|
||||
mSpritePanel.Draw(entity);
|
||||
mMeshPanel.Draw(entity);
|
||||
@@ -127,6 +129,7 @@ void EditorSelectionPanel::DrawEntity(Nuake::Entity entity)
|
||||
mMeshColliderPanel.Draw(entity);
|
||||
mCharacterControllerPanel.Draw(entity);
|
||||
mAudioEmitterPanel.Draw(entity);
|
||||
|
||||
}
|
||||
|
||||
void EditorSelectionPanel::DrawAddComponentMenu(Nuake::Entity entity)
|
||||
@@ -144,6 +147,7 @@ void EditorSelectionPanel::DrawAddComponentMenu(Nuake::Entity entity)
|
||||
if (ImGui::BeginPopup("ComponentPopup"))
|
||||
{
|
||||
MenuItemComponent("Wren Script", WrenScriptComponent);
|
||||
MenuItemComponent("C# Script", NetScriptComponent);
|
||||
MenuItemComponent("Camera", CameraComponent);
|
||||
MenuItemComponent("Light", LightComponent);
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../ComponentsPanel/SkinnedModelPanel.h"
|
||||
#include "../ComponentsPanel/BonePanel.h"
|
||||
#include "../ComponentsPanel/AudioEmitterPanel.h"
|
||||
#include "../ComponentsPanel/NetScriptPanel.h"
|
||||
|
||||
class EditorSelectionPanel
|
||||
{
|
||||
@@ -30,6 +31,7 @@ private:
|
||||
TransformPanel mTransformPanel;
|
||||
LightPanel mLightPanel;
|
||||
ScriptPanel mScriptPanel;
|
||||
NetScriptPanel mNetScriptPanel;
|
||||
MeshPanel mMeshPanel;
|
||||
SkinnedModelPanel mSkinnedModelPanel;
|
||||
QuakeMapPanel mQuakeMapPanel;
|
||||
|
||||
@@ -274,6 +274,10 @@ namespace Nuake
|
||||
{
|
||||
dragType = "_Script";
|
||||
}
|
||||
else if (fileExtension == ".cs")
|
||||
{
|
||||
dragType = "_NetScript";
|
||||
}
|
||||
else if (fileExtension == ".map")
|
||||
{
|
||||
dragType = "_Map";
|
||||
|
||||
@@ -226,18 +226,31 @@ namespace Nuake
|
||||
if (!absolute)
|
||||
finalPath = Root + path;
|
||||
|
||||
std::ifstream MyReadFile(finalPath);
|
||||
std::ifstream myReadFile(finalPath, std::ios::in | std::ios::binary);
|
||||
std::string fileContent = "";
|
||||
std::string allFile = "";
|
||||
|
||||
char bom[3];
|
||||
myReadFile.read(bom, 3);
|
||||
|
||||
// Check for UTF-8 BOM (EF BB BF)
|
||||
if (bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF)
|
||||
{
|
||||
myReadFile.seekg(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
myReadFile.seekg(0);
|
||||
}
|
||||
|
||||
// Use a while loop together with the getline() function to read the file line by line
|
||||
while (getline(MyReadFile, fileContent))
|
||||
while (getline(myReadFile, fileContent))
|
||||
{
|
||||
allFile.append(fileContent + "\n");
|
||||
}
|
||||
|
||||
// Close the file
|
||||
MyReadFile.close();
|
||||
myReadFile.close();
|
||||
return allFile;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +59,13 @@ namespace Nuake
|
||||
Image,
|
||||
Material,
|
||||
Script,
|
||||
NetScript,
|
||||
Project,
|
||||
Prefab,
|
||||
Scene,
|
||||
Wad,
|
||||
Map,
|
||||
Assembly
|
||||
};
|
||||
|
||||
class File
|
||||
@@ -124,6 +126,16 @@ namespace Nuake
|
||||
return FileType::Map;
|
||||
}
|
||||
|
||||
if (ext == ".dll")
|
||||
{
|
||||
return FileType::Assembly;
|
||||
}
|
||||
|
||||
if (ext == ".cs")
|
||||
{
|
||||
return FileType::NetScript;
|
||||
}
|
||||
|
||||
return FileType::Unkown;
|
||||
}
|
||||
|
||||
@@ -170,6 +182,16 @@ namespace Nuake
|
||||
return "Map";
|
||||
}
|
||||
|
||||
if (ext == ".map")
|
||||
{
|
||||
return "Assembly";
|
||||
}
|
||||
|
||||
if (ext == ".cs")
|
||||
{
|
||||
return "C# Script";
|
||||
}
|
||||
|
||||
return "File";
|
||||
}
|
||||
std::string Read()
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
#include "BoxCollider.h"
|
||||
#include "AudioEmitterComponent.h"
|
||||
#include "WrenScriptComponent.h"
|
||||
#include "NetScriptComponent.h"
|
||||
#include "../Entities/Entity.h"
|
||||
|
||||
39
Nuake/src/Scene/Components/NetScriptComponent.h
Normal file
39
Nuake/src/Scene/Components/NetScriptComponent.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include "src/Core/Core.h"
|
||||
#include "src/Core/FileSystem.h"
|
||||
#include "src/Core/Logger.h"
|
||||
#include "src/Resource/Serializable.h"
|
||||
#include "src/Resource/File.h"
|
||||
|
||||
namespace Nuake {
|
||||
|
||||
class NetScriptComponent
|
||||
{
|
||||
public:
|
||||
std::string ScriptPath;
|
||||
std::string Class;
|
||||
|
||||
json Serialize()
|
||||
{
|
||||
BEGIN_SERIALIZE();
|
||||
SERIALIZE_VAL(ScriptPath);
|
||||
SERIALIZE_VAL(Class);
|
||||
END_SERIALIZE();
|
||||
}
|
||||
|
||||
bool Deserialize(const json& j)
|
||||
{
|
||||
if (j.contains("ScriptPath"))
|
||||
{
|
||||
ScriptPath = j["ScriptPath"];
|
||||
}
|
||||
|
||||
if (j.contains("Class"))
|
||||
{
|
||||
Class = j["Class"];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -77,6 +77,8 @@ namespace Nuake
|
||||
SERIALIZE_OBJECT_REF_LBL("BoneComponent", GetComponent<BoneComponent>())
|
||||
if (HasComponent<AudioEmitterComponent>())
|
||||
SERIALIZE_OBJECT_REF_LBL("AudioEmitterComponent", GetComponent<AudioEmitterComponent>())
|
||||
if (HasComponent<NetScriptComponent>())
|
||||
SERIALIZE_OBJECT_REF_LBL("NetScriptComponent", GetComponent<NetScriptComponent>())
|
||||
END_SERIALIZE();
|
||||
}
|
||||
|
||||
@@ -108,6 +110,7 @@ namespace Nuake
|
||||
DESERIALIZE_COMPONENT(BoneComponent);
|
||||
DESERIALIZE_COMPONENT(SkinnedModelComponent);
|
||||
DESERIALIZE_COMPONENT(AudioEmitterComponent);
|
||||
DESERIALIZE_COMPONENT(NetScriptComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ScriptingSystem.h"
|
||||
#include "src/Scene/Components/WrenScriptComponent.h"
|
||||
#include "src/Scene/Components/NetScriptComponent.h"
|
||||
#include "src/Scene/Scene.h"
|
||||
#include "Engine.h"
|
||||
|
||||
@@ -17,14 +18,12 @@ namespace Nuake
|
||||
{
|
||||
ScriptingEngine::Init();
|
||||
|
||||
ScriptingEngineNet::Get().Initialize();
|
||||
|
||||
Logger::Log("Initializing ScriptingSystem");
|
||||
|
||||
auto entities = m_Scene->m_Registry.view<WrenScriptComponent>();
|
||||
for (auto& e : entities)
|
||||
auto wrenEntities = m_Scene->m_Registry.view<WrenScriptComponent>();
|
||||
for (auto& e : wrenEntities)
|
||||
{
|
||||
WrenScriptComponent& wren = entities.get<WrenScriptComponent>(e);
|
||||
WrenScriptComponent& wren = wrenEntities.get<WrenScriptComponent>(e);
|
||||
|
||||
if (!wren.mWrenScript)
|
||||
continue;
|
||||
@@ -41,6 +40,27 @@ namespace Nuake
|
||||
wren.mWrenScript->CallInit();
|
||||
}
|
||||
|
||||
auto& scriptingEngineNet = ScriptingEngineNet::Get();
|
||||
scriptingEngineNet.Initialize();
|
||||
scriptingEngineNet.LoadProjectAssembly(Engine::GetProject());
|
||||
|
||||
auto netEntities = m_Scene->m_Registry.view<NetScriptComponent>();
|
||||
for (auto& e : netEntities)
|
||||
{
|
||||
NetScriptComponent& netScriptComponent = netEntities.get<NetScriptComponent>(e);
|
||||
|
||||
if (netScriptComponent.ScriptPath.empty())
|
||||
continue;
|
||||
|
||||
// Creates an instance of the entity script in C#
|
||||
auto entity = Entity{ e, m_Scene };
|
||||
scriptingEngineNet.RegisterEntityScript(entity);
|
||||
|
||||
// We can now call on init on it.
|
||||
auto scriptInstance = scriptingEngineNet.GetEntityScript(entity);
|
||||
scriptInstance.InvokeMethod("OnInit");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -57,6 +77,20 @@ namespace Nuake
|
||||
if (wren.mWrenScript != nullptr)
|
||||
wren.mWrenScript->CallUpdate(ts);
|
||||
}
|
||||
|
||||
auto& scriptingEngineNet = ScriptingEngineNet::Get();
|
||||
auto netEntities = m_Scene->m_Registry.view<NetScriptComponent>();
|
||||
for (auto& e : netEntities)
|
||||
{
|
||||
NetScriptComponent& netScriptComponent = netEntities.get<NetScriptComponent>(e);
|
||||
|
||||
if (netScriptComponent.ScriptPath.empty())
|
||||
continue;
|
||||
|
||||
auto entity = Entity{ e, m_Scene };
|
||||
auto scriptInstance = scriptingEngineNet.GetEntityScript(entity);
|
||||
scriptInstance.InvokeMethod("OnUpdate", ts.GetSeconds());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +107,20 @@ namespace Nuake
|
||||
if (wren.mWrenScript != nullptr)
|
||||
wren.mWrenScript->CallFixedUpdate(ts);
|
||||
}
|
||||
|
||||
auto& scriptingEngineNet = ScriptingEngineNet::Get();
|
||||
auto netEntities = m_Scene->m_Registry.view<NetScriptComponent>();
|
||||
for (auto& e : netEntities)
|
||||
{
|
||||
NetScriptComponent& netScriptComponent = netEntities.get<NetScriptComponent>(e);
|
||||
|
||||
if (netScriptComponent.ScriptPath.empty())
|
||||
continue;
|
||||
|
||||
auto entity = Entity{ e, m_Scene };
|
||||
auto scriptInstance = scriptingEngineNet.GetEntityScript(entity);
|
||||
scriptInstance.InvokeMethod("OnFixedUpdate", ts.GetSeconds());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +142,22 @@ namespace Nuake
|
||||
|
||||
}
|
||||
|
||||
auto& scriptingEngineNet = ScriptingEngineNet::Get();
|
||||
auto netEntities = m_Scene->m_Registry.view<NetScriptComponent>();
|
||||
for (auto& e : netEntities)
|
||||
{
|
||||
NetScriptComponent& netScriptComponent = netEntities.get<NetScriptComponent>(e);
|
||||
|
||||
if (netScriptComponent.ScriptPath.empty())
|
||||
continue;
|
||||
|
||||
// Creates an instance of the entity script in C#
|
||||
auto entity = Entity{ e, m_Scene };
|
||||
auto scriptInstance = scriptingEngineNet.GetEntityScript(entity);
|
||||
scriptInstance.InvokeMethod("OnDestroy");
|
||||
}
|
||||
|
||||
ScriptingEngine::Close();
|
||||
ScriptingEngineNet::Get().Uninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
15
Nuake/src/Scripting/NetModules/EngineNetAPI.cpp
Normal file
15
Nuake/src/Scripting/NetModules/EngineNetAPI.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "EngineNetAPI.h"
|
||||
|
||||
namespace Nuake {
|
||||
|
||||
void Log(Coral::NativeString string)
|
||||
{
|
||||
Logger::Log(string.ToString(), ".net", VERBOSE);
|
||||
}
|
||||
|
||||
void EngineNetAPI::RegisterMethods()
|
||||
{
|
||||
RegisterMethod("LoggerLogIcall", (void*)(&Log));
|
||||
}
|
||||
|
||||
}
|
||||
14
Nuake/src/Scripting/NetModules/EngineNetAPI.h
Normal file
14
Nuake/src/Scripting/NetModules/EngineNetAPI.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "NetAPIModule.h"
|
||||
|
||||
namespace Nuake {
|
||||
|
||||
class EngineNetAPI : public NetAPIModule
|
||||
{
|
||||
public:
|
||||
virtual const std::string GetModuleName() const override { return "Engine"; }
|
||||
|
||||
virtual void RegisterMethods() override;
|
||||
|
||||
};
|
||||
}
|
||||
25
Nuake/src/Scripting/NetModules/NetAPIModule.h
Normal file
25
Nuake/src/Scripting/NetModules/NetAPIModule.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "src/Core/Core.h"
|
||||
#include "src/Core/Logger.h"
|
||||
|
||||
#include <Coral/NativeString.hpp>
|
||||
|
||||
namespace Nuake {
|
||||
|
||||
class NetAPIModule
|
||||
{
|
||||
public:
|
||||
virtual const std::string GetModuleName() const = 0;
|
||||
virtual void RegisterMethods() = 0;
|
||||
|
||||
std::unordered_map<std::string, void*> GetMethods() const { return m_Methods; }
|
||||
protected:
|
||||
void RegisterMethod(const std::string& name, void* methodPtr)
|
||||
{
|
||||
m_Methods.emplace(name, methodPtr);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, void*> m_Methods;
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
#include "ScriptingEngine.h"
|
||||
#include "WrenScript.h"
|
||||
#include "src/Core/FileSystem.h"
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
#include "src/Core/Logger.h"
|
||||
#include "src/Core/FileSystem.h"
|
||||
#include "src/Core/OS.h"
|
||||
#include "src/Resource/Project.h"
|
||||
|
||||
#include "NetModules/EngineNetAPI.h"
|
||||
|
||||
#include <Coral/HostInstance.hpp>
|
||||
#include <Coral/GC.hpp>
|
||||
#include <Coral/NativeArray.hpp>
|
||||
#include <Coral/Attribute.hpp>
|
||||
#include <src/Scene/Components/NetScriptComponent.h>
|
||||
|
||||
|
||||
void ExceptionCallback(std::string_view InMessage)
|
||||
@@ -19,16 +24,21 @@ namespace Nuake
|
||||
{
|
||||
ScriptingEngineNet::ScriptingEngineNet()
|
||||
{
|
||||
auto coralDir = "";
|
||||
Coral::HostSettings settings =
|
||||
{
|
||||
.CoralDirectory = coralDir,
|
||||
.ExceptionCallback = ExceptionCallback
|
||||
};
|
||||
m_HostInstance = new Coral::HostInstance();
|
||||
m_HostInstance->Initialize(settings);
|
||||
|
||||
// Initialize Coral
|
||||
// ----------------------------------
|
||||
|
||||
|
||||
// Initialize API modules
|
||||
// ----------------------------------
|
||||
m_Modules =
|
||||
{
|
||||
CreateRef<EngineNetAPI>()
|
||||
};
|
||||
|
||||
for (auto& m : m_Modules)
|
||||
{
|
||||
m->RegisterMethods();
|
||||
}
|
||||
}
|
||||
|
||||
ScriptingEngineNet::~ScriptingEngineNet()
|
||||
@@ -42,30 +52,241 @@ namespace Nuake
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Log(Coral::NativeString string)
|
||||
{
|
||||
Logger::Log(string.ToString(), ".net", VERBOSE);
|
||||
}
|
||||
|
||||
void ScriptingEngineNet::Initialize()
|
||||
{
|
||||
auto loadContext = m_HostInstance->CreateAssemblyLoadContext("NuakeEngineContext");
|
||||
Coral::HostSettings settings =
|
||||
{
|
||||
.CoralDirectory = "",
|
||||
.ExceptionCallback = ExceptionCallback
|
||||
};
|
||||
|
||||
auto& assembly = loadContext.LoadAssembly("NuakeNet.dll");
|
||||
assembly.AddInternalCall("Nuake.Net.Engine", "LoggerLogIcall", reinterpret_cast<void*>(&Log));
|
||||
assembly.UploadInternalCalls();
|
||||
m_HostInstance = new Coral::HostInstance();
|
||||
m_HostInstance->Initialize(settings);
|
||||
|
||||
auto& engineType = assembly.GetType("Nuake.Net.Engine");
|
||||
auto engineInstance = engineType.CreateInstance();
|
||||
m_LoadContext = std::move(m_HostInstance->CreateAssemblyLoadContext(m_ContextName));
|
||||
|
||||
Coral::NativeString param1 = Coral::NativeString::FromUTF8("Hello from CPP");;
|
||||
engineInstance.InvokeMethod("Log", std::string("Hello from CPP"));
|
||||
// Load Nuake assembly DLL
|
||||
const std::string absoluteAssemblyPath = FileSystem::Root + m_NetDirectory + "/" + m_EngineAssemblyName;
|
||||
m_NuakeAssembly = m_LoadContext.LoadAssembly(absoluteAssemblyPath);
|
||||
|
||||
engineInstance.Destroy();
|
||||
// Upload internal calls for each module
|
||||
// --------------------------------------------------
|
||||
for (const auto& netModule : m_Modules)
|
||||
{
|
||||
const std::string inClassName = m_Scope + '.' + netModule->GetModuleName();
|
||||
for (const auto& [methodName, methodPtr] : netModule->GetMethods())
|
||||
{
|
||||
m_NuakeAssembly.AddInternalCall(inClassName, methodName, methodPtr);
|
||||
}
|
||||
}
|
||||
|
||||
Coral::GC::Collect();
|
||||
m_NuakeAssembly.UploadInternalCalls();
|
||||
}
|
||||
|
||||
void ScriptingEngineNet::Uninitialize()
|
||||
{
|
||||
// We have to manually destroy every managed object we have created
|
||||
for (auto& [entity, managedObject] : m_EntityToManagedObjects)
|
||||
{
|
||||
managedObject.Destroy();
|
||||
}
|
||||
|
||||
Coral::GC::Collect(1, Coral::GCCollectionMode::Forced);
|
||||
Coral::GC::WaitForPendingFinalizers();
|
||||
|
||||
m_HostInstance->UnloadAssemblyLoadContext(loadContext);
|
||||
m_HostInstance->UnloadAssemblyLoadContext(m_LoadContext);
|
||||
|
||||
m_EntityToManagedObjects.clear();
|
||||
m_GameEntityTypes.clear();
|
||||
}
|
||||
|
||||
void ScriptingEngineNet::LoadProjectAssembly(Ref<Project> project)
|
||||
{
|
||||
const std::string sanitizedProjectName = String::Sanitize(project->Name);
|
||||
const std::string assemblyPath = "/bin/Debug/net7.0/" + sanitizedProjectName + ".dll";
|
||||
|
||||
if (!FileSystem::FileExists(assemblyPath))
|
||||
{
|
||||
Logger::Log("Couldn't load .net assembly. Did you forget to build the .net project?", ".net", CRITICAL);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string absoluteAssemblyPath = FileSystem::Root + assemblyPath;
|
||||
m_GameAssembly = m_LoadContext.LoadAssembly(absoluteAssemblyPath);
|
||||
|
||||
for (auto& type : m_GameAssembly.GetTypes())
|
||||
{
|
||||
Logger::Log(std::string("Detected type: ") + std::string(type->GetName()), ".net");
|
||||
Logger::Log(std::string("Detected base type: ") + std::string(type->GetBaseType().GetName()), ".net");
|
||||
|
||||
const std::string baseTypeName = std::string(type->GetBaseType().GetName());
|
||||
if (baseTypeName == "Entity")
|
||||
{
|
||||
// We have found an entity script.
|
||||
m_GameEntityTypes[std::string(type->GetName())] = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptingEngineNet::RegisterEntityScript(Entity& entity)
|
||||
{
|
||||
if (!entity.IsValid())
|
||||
{
|
||||
Logger::Log("Failed to register entity .net script: Entity not valid.", ".net", CRITICAL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.HasComponent<NetScriptComponent>())
|
||||
{
|
||||
Logger::Log("Failed to register entity .net script: Entity doesn't have a .net script component.", ".net", CRITICAL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& component = entity.GetComponent<NetScriptComponent>();
|
||||
const auto& filePath = component.ScriptPath;
|
||||
if (filePath.empty())
|
||||
{
|
||||
Logger::Log("Skipped .net entity script since it was empty.", ".net", VERBOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FileSystem::FileExists(filePath))
|
||||
{
|
||||
Logger::Log("Skipped .net entity script: The file path doesn't exist.", ".net", WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
// We can now scan the file and look for this pattern: class XXXXX : Entity
|
||||
// Warning, this doesnt do any bound check so if there is a semicolon at the end
|
||||
// of the file. IT MIGHT CRASH HERE. POTENTIALLY - I have not tested.
|
||||
// -----------------------------------------------------
|
||||
std::string fileContent = FileSystem::ReadFile(filePath);
|
||||
fileContent = fileContent.substr(3, fileContent.size());
|
||||
fileContent = String::RemoveWhiteSpace(fileContent);
|
||||
|
||||
// Find class token
|
||||
size_t classTokenPos = fileContent.find("class");
|
||||
if (classTokenPos == std::string::npos)
|
||||
{
|
||||
Logger::Log("Skipped .net entity script: file doesnt contain entity class.", ".net", WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t classNameStartIndex = classTokenPos + 5; // 4 letter: class + 1 for next char
|
||||
|
||||
// Find semi-colon token
|
||||
size_t semiColonPos = fileContent.find(":");
|
||||
if (semiColonPos == std::string::npos || semiColonPos < classTokenPos)
|
||||
{
|
||||
Logger::Log("Skipped .net entity script: Not class inheriting Entity was found.", ".net", WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t classNameLength = semiColonPos - classNameStartIndex;
|
||||
|
||||
const std::string className = fileContent.substr(classNameStartIndex, classNameLength);
|
||||
|
||||
if(m_GameEntityTypes.find(className) == m_GameEntityTypes.end())
|
||||
{
|
||||
// The class name parsed in the file was not found in the game's DLL.
|
||||
const std::string& msg = "Skipped .net entity script: \n Class: " +
|
||||
className + " not found in " + std::string(m_GameAssembly.GetName());
|
||||
Logger::Log(msg, ".net", CRITICAL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto classInstance = m_GameEntityTypes[className]->CreateInstance();
|
||||
m_EntityToManagedObjects.emplace(entity.GetID(), classInstance);
|
||||
}
|
||||
|
||||
Coral::ManagedObject ScriptingEngineNet::GetEntityScript(const Entity& entity)
|
||||
{
|
||||
if (!HasEntityScriptInstance(entity))
|
||||
{
|
||||
Logger::Log("Failed to get entity .Net script instance, doesn't exist", ".net", CRITICAL);
|
||||
throw std::exception("Failed to get entity .Net script instance, doesn't exist");
|
||||
}
|
||||
|
||||
return m_EntityToManagedObjects[entity.GetID()];
|
||||
}
|
||||
|
||||
bool ScriptingEngineNet::HasEntityScriptInstance(const Entity& entity)
|
||||
{
|
||||
return m_EntityToManagedObjects.find(entity.GetID()) != m_EntityToManagedObjects.end();
|
||||
}
|
||||
|
||||
void ScriptingEngineNet::GenerateSolution(const std::string& path, const std::string& projectName)
|
||||
{
|
||||
// Create .Net directory in projects folder.
|
||||
const auto netDirectionPath = '/' + m_NetDirectory + '/';
|
||||
const auto netDir = path + netDirectionPath;
|
||||
if (!FileSystem::DirectoryExists(netDirectionPath))
|
||||
{
|
||||
FileSystem::MakeDirectory(netDirectionPath);
|
||||
}
|
||||
|
||||
// Copy engine assembly to projects folder.
|
||||
const std::vector<std::string> dllToCopy =
|
||||
{
|
||||
m_EngineAssemblyName
|
||||
};
|
||||
|
||||
for (const auto& fileToCopy : dllToCopy)
|
||||
{
|
||||
std::filesystem::copy_file(fileToCopy, netDir + fileToCopy, std::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
|
||||
// Generate premake5 templates
|
||||
// ----------------------------------------
|
||||
const std::string cleanProjectName = String::Sanitize(projectName);
|
||||
const std::string premakeScript = R"(
|
||||
workspace ")" + cleanProjectName + R"("
|
||||
project ")" + cleanProjectName + R"("
|
||||
language "C#"
|
||||
dotnetframework "net7.0"
|
||||
|
||||
kind "SharedLib"
|
||||
clr "Unsafe"
|
||||
|
||||
-- Don't specify architecture here. (see https://github.com/premake/premake-core/issues/1758)
|
||||
|
||||
files
|
||||
{
|
||||
"**.cs"
|
||||
}
|
||||
|
||||
links
|
||||
{
|
||||
".net/NuakeNet"
|
||||
}
|
||||
)";
|
||||
|
||||
// Writting premake5 templates in project's directory
|
||||
// ----------------------------------------
|
||||
FileSystem::BeginWriteFile("premake5.lua");
|
||||
FileSystem::WriteLine(premakeScript);
|
||||
FileSystem::EndWriteFile();
|
||||
|
||||
// Execute premake script, generating .sln
|
||||
OS::ExecuteCommand("cd " + path + " && premake5 vs2022");
|
||||
|
||||
// Open solution file in visual studio
|
||||
OS::OpenIn(path + cleanProjectName + ".sln");
|
||||
|
||||
// Delete premake script
|
||||
FileSystem::DeleteFileFromPath(FileSystem::Root + "/premake5.lua");
|
||||
}
|
||||
|
||||
void ScriptingEngineNet::CreateEntityScript(const std::string & path, const std::string& entityName)
|
||||
{
|
||||
const std::string scriptTemplate = R"(
|
||||
using )" + m_Scope + R"(;
|
||||
|
||||
namespace NuakeShowcase
|
||||
{
|
||||
class )" + entityName + R"(
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,44 @@
|
||||
#pragma once
|
||||
#include "src/Core/Core.h"
|
||||
#include "src/Scene/Entities/Entity.h"
|
||||
|
||||
|
||||
|
||||
#include "src/Scripting/NetModules/NetAPIModule.h"
|
||||
|
||||
namespace Coral
|
||||
{
|
||||
class HostInstance;
|
||||
class AssemblyLoadContext;
|
||||
class Type;
|
||||
}
|
||||
|
||||
namespace Nuake
|
||||
{
|
||||
#include <Coral/Assembly.hpp>
|
||||
|
||||
namespace Nuake {
|
||||
|
||||
class Project;
|
||||
|
||||
class ScriptingEngineNet
|
||||
{
|
||||
private:
|
||||
const std::string m_Scope = "Nuake.Net";
|
||||
const std::string m_EngineAssemblyName = "NuakeNet.dll";
|
||||
const std::string m_NetDirectory = ".net";
|
||||
const std::string m_ContextName = "NuakeEngineContext";
|
||||
|
||||
Coral::HostInstance* m_HostInstance;
|
||||
Coral::AssemblyLoadContext* m_LoadContext;
|
||||
Coral::AssemblyLoadContext m_LoadContext;
|
||||
|
||||
std::unordered_map<std::string, Coral::AssemblyLoadContext*> m_LoadedAssemblies;
|
||||
std::vector<Ref<NetAPIModule>> m_Modules;
|
||||
|
||||
Coral::ManagedAssembly m_NuakeAssembly; // Nuake DLL
|
||||
Coral::ManagedAssembly m_GameAssembly; // Game DLL
|
||||
|
||||
// This is a map of all the entity scripts detected in the game's assembly.
|
||||
// This is filled when loading the game's assembly.
|
||||
|
||||
// This is a map that contains all the instances of entity scripts.
|
||||
std::unordered_map<std::string, Coral::Type*> m_GameEntityTypes;
|
||||
std::unordered_map<uint32_t, Coral::ManagedObject> m_EntityToManagedObjects;
|
||||
|
||||
ScriptingEngineNet();
|
||||
~ScriptingEngineNet();
|
||||
@@ -25,5 +47,15 @@ namespace Nuake
|
||||
static ScriptingEngineNet& Get();
|
||||
|
||||
void Initialize();
|
||||
void Uninitialize();
|
||||
|
||||
void LoadProjectAssembly(Ref<Project> project);
|
||||
|
||||
void RegisterEntityScript(Entity& entity);
|
||||
Coral::ManagedObject GetEntityScript(const Entity& entity);
|
||||
bool HasEntityScriptInstance(const Entity& entity);
|
||||
|
||||
void GenerateSolution(const std::string& path, const std::string& projectName);
|
||||
void CreateEntityScript(const std::string& path, const std::string& entityName);
|
||||
};
|
||||
}
|
||||
@@ -19,4 +19,9 @@ project "NuakeNet"
|
||||
links
|
||||
{
|
||||
"Coral.Managed"
|
||||
}
|
||||
}
|
||||
|
||||
postbuildcommands {
|
||||
'{ECHO} Copying "%{wks.location}/NuakeNet/bin/%{cfg.buildcfg}/NuakeNet.dll" to "%{wks.location}/Editor"',
|
||||
'{COPYFILE} "%{wks.location}/NuakeNet/bin/%{cfg.buildcfg}/NuakeNet.dll" "%{wks.location}/Editor"'
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace Nuake.Net
|
||||
public class Engine
|
||||
{
|
||||
internal static unsafe delegate*<NativeString, void> LoggerLogIcall;
|
||||
|
||||
|
||||
public Engine() { }
|
||||
|
||||
@@ -21,9 +20,9 @@ namespace Nuake.Net
|
||||
/// Prints a message to the console log
|
||||
/// </summary>
|
||||
/// <param name="message">message to be printed</param>
|
||||
public void Log(NativeString input)
|
||||
public static void Log(string input)
|
||||
{
|
||||
unsafe { LoggerLogIcall(input + " - hello from c# man"); }
|
||||
unsafe { LoggerLogIcall(input); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,17 +63,12 @@ namespace Nuake.Net
|
||||
|
||||
public UInt32 ID { get; private set; }
|
||||
|
||||
Entity(UInt32 id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
public virtual void OnInit() { }
|
||||
public virtual void OnUpdate(float dt) { }
|
||||
public virtual void OnFixedUpdate(float dt) { }
|
||||
public virtual void OnDestroy() { }
|
||||
|
||||
bool HasComponent<T>()
|
||||
public bool HasComponent<T>()
|
||||
{
|
||||
if (typeof(T) == typeof(TransformComponent))
|
||||
{
|
||||
@@ -88,7 +82,7 @@ namespace Nuake.Net
|
||||
return false;
|
||||
}
|
||||
|
||||
T GetComponent<T>()
|
||||
public T GetComponent<T>()
|
||||
{
|
||||
if (typeof(T) == typeof(TransformComponent))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user