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:
Antoine Pilote
2023-10-16 00:21:28 -04:00
parent 9807f0fa8f
commit af2546fb21
21 changed files with 677 additions and 50 deletions

5
.gitignore vendored
View File

@@ -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

View 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", &currentModule, &modulesC[0], modules.size());
// component.mModule = currentModule;
// }
//
// ImGui::TableNextColumn();
// //ComponentTableReset(component.Class, "");
//}
}
EndComponentTable();
}

View 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;
};

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -274,6 +274,10 @@ namespace Nuake
{
dragType = "_Script";
}
else if (fileExtension == ".cs")
{
dragType = "_NetScript";
}
else if (fileExtension == ".map")
{
dragType = "_Map";

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -24,4 +24,5 @@
#include "BoxCollider.h"
#include "AudioEmitterComponent.h"
#include "WrenScriptComponent.h"
#include "NetScriptComponent.h"
#include "../Entities/Entity.h"

View 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;
}
};
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View 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));
}
}

View 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;
};
}

View 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;
};
}

View File

@@ -1,3 +1,4 @@
#include "ScriptingEngine.h"
#include "WrenScript.h"
#include "src/Core/FileSystem.h"

View File

@@ -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"(
}
)";
}
}

View File

@@ -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);
};
}

View File

@@ -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"'
}

View File

@@ -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))
{