diff --git a/Editor/src/ComponentsPanel/NetScriptPanel.cpp b/Editor/src/ComponentsPanel/NetScriptPanel.cpp index 2f0f3f49..b1a23f62 100644 --- a/Editor/src/ComponentsPanel/NetScriptPanel.cpp +++ b/Editor/src/ComponentsPanel/NetScriptPanel.cpp @@ -2,6 +2,7 @@ #include "../Windows/FileSystemUI.h" #include #include +#include void NetScriptPanel::Draw(Nuake::Entity entity) { @@ -77,9 +78,71 @@ void NetScriptPanel::Draw(Nuake::Entity entity) ComponentTableReset(component.ScriptPath, ""); } - //ImGui::TableNextColumn(); - //{ - // ImGui::Text("Module"); + + std::vector detectedExposedVar; + for (auto& e : Nuake::ScriptingEngineNet::Get().GetExposedVarForTypes(entity)) + { + bool found = false; + for (auto& c : component.ExposedVar) + { + if (e.Name == c.Name) + { + c.Type = (Nuake::NetScriptExposedVarType)e.Type; + c.DefaultValue = e.Value; + found = true; + } + } + + detectedExposedVar.push_back(e.Name); + + if (!found) + { + Nuake::NetScriptExposedVar exposedVar; + exposedVar.Name = e.Name; + exposedVar.Value = e.Value; + exposedVar.DefaultValue = e.Value; + exposedVar.Type = (Nuake::NetScriptExposedVarType)e.Type; + component.ExposedVar.push_back(exposedVar); + } + } + + // If we havent loaded the DLL, we cant trust the exposed var returned my the engine. + if (Nuake::ScriptingEngineNet::Get().IsInitialized()) + { + // Erase all exposed var from the component that dont exist in the DLL anymore. + std::erase_if(component.ExposedVar, + [&](Nuake::NetScriptExposedVar& var) + { + return std::find(detectedExposedVar.begin(), detectedExposedVar.end(), var.Name) == detectedExposedVar.end(); + } + ); + } + + for (auto& field : component.ExposedVar) + { + ImGui::TableNextColumn(); + { + ImGui::Text(field.Name.c_str()); + ImGui::TableNextColumn(); + + if (field.Type == Nuake::NetScriptExposedVarType::Float) + { + if (!field.Value.has_value()) + { + field.Value = field.DefaultValue; + } + + float currentValue = std::any_cast(field.Value); + const std::string sliderName = "##" + field.Name + "slider"; + ImGui::DragFloat(sliderName.c_str(), ¤tValue); + field.Value = currentValue; + } + + ImGui::TableNextColumn(); + } + } + + // // ImGui::TableNextColumn(); // // // Here we create a dropdown for every modules @@ -97,7 +160,6 @@ void NetScriptPanel::Draw(Nuake::Entity entity) // static int currentModule = (int)component.mModule; // ImGui::Combo("##WrenModule", ¤tModule, &modulesC[0], modules.size()); // component.mModule = currentModule; - // } // // ImGui::TableNextColumn(); // //ComponentTableReset(component.Class, ""); diff --git a/Nuake/src/Scene/Components/NetScriptComponent.h b/Nuake/src/Scene/Components/NetScriptComponent.h index 0e740ff7..14c55331 100644 --- a/Nuake/src/Scene/Components/NetScriptComponent.h +++ b/Nuake/src/Scene/Components/NetScriptComponent.h @@ -4,9 +4,30 @@ #include "src/Core/Logger.h" #include "src/Resource/Serializable.h" #include "src/Resource/File.h" +#include namespace Nuake { + enum class NetScriptExposedVarType + { + Bool, + Float, + Double, + String, + Entity, + Vector2, + Vector3, + Vector4 + }; + + struct NetScriptExposedVar + { + std::string Name; + std::any Value; + std::any DefaultValue; + NetScriptExposedVarType Type; + }; + class NetScriptComponent { public: @@ -15,11 +36,51 @@ namespace Nuake { bool Initialized = false; + std::vector ExposedVar; + json Serialize() { BEGIN_SERIALIZE(); SERIALIZE_VAL(ScriptPath); SERIALIZE_VAL(Class); + + json exposedVarJson; + int i = 0; + for (auto& e : ExposedVar) + { + json varJ; + varJ["Name"] = e.Name; + varJ["Type"] = e.Type; + + switch (e.Type) + { + case NetScriptExposedVarType::Bool: + varJ["Value"] = std::any_cast(e.Value); + varJ["DefaultValue"] = std::any_cast(e.DefaultValue); + break; + case NetScriptExposedVarType::Float: + varJ["Value"] = std::any_cast(e.Value); + varJ["DefaultValue"] = std::any_cast(e.DefaultValue); + break; + case NetScriptExposedVarType::Double: + varJ["Value"] = std::any_cast(e.Value); + varJ["DefaultValue"] = std::any_cast(e.DefaultValue); + break; + case NetScriptExposedVarType::String: + varJ["Value"] = std::any_cast(e.Value); + varJ["DefaultValue"] = std::any_cast(e.DefaultValue); + break; + default: + varJ["Value"] = 0; + break; + } + + exposedVarJson[i] = varJ; + i++; + } + + j["ExposedVar"] = exposedVarJson; + END_SERIALIZE(); } @@ -35,6 +96,40 @@ namespace Nuake { Class = j["Class"]; } + if (j.contains("ExposedVar")) + { + for (auto& exposedVarJson : j["ExposedVar"]) + { + NetScriptExposedVar exposedVar; + exposedVar.Name = exposedVarJson["Name"]; + exposedVar.Type = exposedVarJson["Type"]; + switch (exposedVar.Type) + { + case NetScriptExposedVarType::Bool: + exposedVar.Value = (bool)exposedVarJson["Value"]; + exposedVar.DefaultValue = (bool)exposedVarJson["DefaultValue"]; + break; + case NetScriptExposedVarType::Float: + exposedVar.Value = (float)exposedVarJson["Value"]; + exposedVar.DefaultValue = (float)exposedVarJson["DefaultValue"]; + break; + case NetScriptExposedVarType::Double: + exposedVar.Value = (double)exposedVarJson["Value"]; + exposedVar.DefaultValue = (double)exposedVarJson["DefaultValue"]; + break; + case NetScriptExposedVarType::String: + exposedVar.Value = (std::string)exposedVarJson["Value"]; + exposedVar.DefaultValue = (std::string)exposedVarJson["DefaultValue"]; + break; + default: + exposedVar.DefaultValue = 0; + break; + } + + ExposedVar.push_back(exposedVar); + } + } + return true; } }; diff --git a/Nuake/src/Scripting/ScriptingEngineNet.cpp b/Nuake/src/Scripting/ScriptingEngineNet.cpp index 6d0366de..8bf9e1a5 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.cpp +++ b/Nuake/src/Scripting/ScriptingEngineNet.cpp @@ -96,6 +96,8 @@ namespace Nuake m_NuakeAssembly.UploadInternalCalls(); m_IsInitialized = true; + + m_GameEntityTypes.clear(); } void ScriptingEngineNet::Uninitialize() @@ -114,10 +116,33 @@ namespace Nuake m_HostInstance->UnloadAssemblyLoadContext(m_LoadContext); - m_GameEntityTypes.clear(); + m_EntityToManagedObjects.clear(); } + std::vector ScriptingEngineNet::GetExposedVarForTypes(Entity entity) + { + if (!entity.HasComponent()) + { + return std::vector(); + } + + auto& component = entity.GetComponent(); + const auto& filePath = component.ScriptPath; + const std::string& className = FindClassNameInScript(filePath); + + 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 std::vector(); + } + + return m_GameEntityTypes[className].exposedVars; + } + void ScriptingEngineNet::BuildProjectAssembly(Ref project) { const std::string sanitizedProjectName = String::Sanitize(project->Name); @@ -149,6 +174,8 @@ namespace Nuake const std::string absoluteAssemblyPath = FileSystem::Root + assemblyPath; m_GameAssembly = m_LoadContext.LoadAssembly(absoluteAssemblyPath); + auto& exposedFieldAttributeType = m_GameAssembly.GetType("Nuake.Net.ExposedAttribute"); + for (auto& type : m_GameAssembly.GetTypes()) { const std::string baseTypeName = std::string(type->GetBaseType().GetFullName()); @@ -156,7 +183,40 @@ namespace Nuake { auto typeSplits = String::Split(type->GetFullName(), '.'); std::string shortenedTypeName = typeSplits[typeSplits.size() - 1]; - m_GameEntityTypes[shortenedTypeName] = type; // We have found an entity script. + + NetGameScriptObject gameScriptObject; + gameScriptObject.coralType = type; + gameScriptObject.exposedVars = std::vector(); + + for (auto& f : type->GetFields()) + { + for (auto& a : f.GetAttributes()) + { + if (a.GetType() == exposedFieldAttributeType) + { + ExposedVar exposedVar; + exposedVar.Name = f.GetName(); + + auto typeName = f.GetType().GetFullName(); + ExposedVarTypes varType = ExposedVarTypes::Float; + if (typeName == "System.Single") + { + varType = ExposedVarTypes::Float; + } + else if (typeName == "System.Double") + { + varType = ExposedVarTypes::Double; + } + + exposedVar.Type = varType; + + gameScriptObject.exposedVars.push_back(exposedVar); + Logger::Log("Exposed field detected: " + std::string(f.GetName()) + " of type " + std::string(f.GetType().GetFullName()) ); + } + } + } + + m_GameEntityTypes[shortenedTypeName] = gameScriptObject; } } } @@ -181,6 +241,7 @@ namespace Nuake } auto& component = entity.GetComponent(); + const auto& filePath = component.ScriptPath; const std::string& className = FindClassNameInScript(filePath); @@ -194,13 +255,54 @@ namespace Nuake return; } - auto classInstance = m_GameEntityTypes[className]->CreateInstance(); + auto classInstance = m_GameEntityTypes[className].coralType->CreateInstance(); int handle = entity.GetHandle(); int id = entity.GetID(); classInstance.SetPropertyValue("ECSHandle", handle); classInstance.SetPropertyValue("ID", id); + std::vector detectedExposedVar; + detectedExposedVar.reserve(std::size(m_GameEntityTypes[className].exposedVars)); + // Update new default values if they have changed in the code. + for (auto& exposedVar : m_GameEntityTypes[className].exposedVars) + { + const std::string varName = exposedVar.Name; + detectedExposedVar.push_back(varName); + + switch (exposedVar.Type) + { + case ExposedVarTypes::Float: + { + exposedVar.Value = classInstance.GetFieldValue(varName); + break; + } + case ExposedVarTypes::Double: + { + exposedVar.Value = classInstance.GetFieldValue(varName); + break; + } + case ExposedVarTypes::String: + { + exposedVar.Value = classInstance.GetFieldValue(varName); + break; + } + case ExposedVarTypes::Entity: + { + exposedVar.Value = classInstance.GetFieldValue(varName); + break; + } + } + } + + + + // Override with user values set in the editor. + for (auto& exposedVarUserValue : component.ExposedVar) + { + classInstance.SetFieldValue(exposedVarUserValue.Name, exposedVarUserValue.Value); + } + m_EntityToManagedObjects.emplace(entity.GetID(), classInstance); } @@ -356,7 +458,7 @@ namespace NuakeShowcase )"; } - std::string ScriptingEngineNet::FindClassNameInScript(const std::string & filePath) + std::string ScriptingEngineNet::FindClassNameInScript(const std::string& filePath) { if (filePath.empty()) { @@ -375,6 +477,11 @@ namespace NuakeShowcase // of the file. IT MIGHT CRASH HERE. POTENTIALLY - I have not tested. // ----------------------------------------------------- std::string fileContent = FileSystem::ReadFile(filePath); + if (fileContent.empty()) + { + return ""; + } + fileContent = fileContent.substr(3, fileContent.size()); fileContent = String::RemoveWhiteSpace(fileContent); diff --git a/Nuake/src/Scripting/ScriptingEngineNet.h b/Nuake/src/Scripting/ScriptingEngineNet.h index c7932475..3fb3c492 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.h +++ b/Nuake/src/Scripting/ScriptingEngineNet.h @@ -3,6 +3,7 @@ #include "src/Scene/Entities/Entity.h" #include "src/Scripting/NetModules/NetAPIModule.h" +#include namespace Coral { @@ -16,6 +17,31 @@ namespace Nuake { class Project; + enum class ExposedVarTypes + { + Bool, + Float, + Double, + String, + Entity, + Vector2, + Vector3, + Vector4 + }; + + struct ExposedVar + { + ExposedVarTypes Type; + std::any Value; + std::string Name; + }; + + struct NetGameScriptObject + { + Coral::Type* coralType; + std::vector exposedVars; + }; + class ScriptingEngineNet { private: @@ -39,7 +65,7 @@ namespace Nuake { // This is filled when loading the game's assembly. // This is a map that contains all the instances of entity scripts. - std::unordered_map m_GameEntityTypes; + std::unordered_map m_GameEntityTypes; std::unordered_map m_EntityToManagedObjects; ScriptingEngineNet(); @@ -51,6 +77,10 @@ namespace Nuake { void Initialize(); void Uninitialize(); + bool IsInitialized() const { return m_IsInitialized; } + + std::vector GetExposedVarForTypes(Entity entity); + void BuildProjectAssembly(Ref project); void LoadProjectAssembly(Ref project); diff --git a/NuakeNet/src/main.cs b/NuakeNet/src/main.cs index 5497cb1a..69a06300 100644 --- a/NuakeNet/src/main.cs +++ b/NuakeNet/src/main.cs @@ -12,7 +12,6 @@ namespace Nuake.Net /// This is the core Nuake.Net API. /// All internal call should happen in this file. /// - public class Engine { internal static unsafe delegate* LoggerLogIcall; @@ -29,6 +28,12 @@ namespace Nuake.Net } } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class ExposedAttribute : Attribute + { + } + public class Debug { internal static unsafe delegate*