Added basic C# field reflection in editor

This commit is contained in:
Antoine Pilote
2024-08-13 21:12:11 -04:00
parent 01a7db2132
commit 9733e9651a
5 changed files with 310 additions and 10 deletions

View File

@@ -2,6 +2,7 @@
#include "../Windows/FileSystemUI.h"
#include <src/Scene/Components/NetScriptComponent.h>
#include <src/Core/FileSystem.h>
#include <src/Scripting/ScriptingEngineNet.h>
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<std::string> 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<float>(field.Value);
const std::string sliderName = "##" + field.Name + "slider";
ImGui::DragFloat(sliderName.c_str(), &currentValue);
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", &currentModule, &modulesC[0], modules.size());
// component.mModule = currentModule;
// }
//
// ImGui::TableNextColumn();
// //ComponentTableReset(component.Class, "");

View File

@@ -4,9 +4,30 @@
#include "src/Core/Logger.h"
#include "src/Resource/Serializable.h"
#include "src/Resource/File.h"
#include <any>
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<NetScriptExposedVar> 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<bool>(e.Value);
varJ["DefaultValue"] = std::any_cast<bool>(e.DefaultValue);
break;
case NetScriptExposedVarType::Float:
varJ["Value"] = std::any_cast<float>(e.Value);
varJ["DefaultValue"] = std::any_cast<float>(e.DefaultValue);
break;
case NetScriptExposedVarType::Double:
varJ["Value"] = std::any_cast<double>(e.Value);
varJ["DefaultValue"] = std::any_cast<double>(e.DefaultValue);
break;
case NetScriptExposedVarType::String:
varJ["Value"] = std::any_cast<std::string>(e.Value);
varJ["DefaultValue"] = std::any_cast<std::string>(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;
}
};

View File

@@ -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<ExposedVar> ScriptingEngineNet::GetExposedVarForTypes(Entity entity)
{
if (!entity.HasComponent<NetScriptComponent>())
{
return std::vector<ExposedVar>();
}
auto& component = entity.GetComponent<NetScriptComponent>();
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<ExposedVar>();
}
return m_GameEntityTypes[className].exposedVars;
}
void ScriptingEngineNet::BuildProjectAssembly(Ref<Project> 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<ExposedVar>();
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<NetScriptComponent>();
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<std::string> 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<float>(varName);
break;
}
case ExposedVarTypes::Double:
{
exposedVar.Value = classInstance.GetFieldValue<double>(varName);
break;
}
case ExposedVarTypes::String:
{
exposedVar.Value = classInstance.GetFieldValue<float>(varName);
break;
}
case ExposedVarTypes::Entity:
{
exposedVar.Value = classInstance.GetFieldValue<int>(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);

View File

@@ -3,6 +3,7 @@
#include "src/Scene/Entities/Entity.h"
#include "src/Scripting/NetModules/NetAPIModule.h"
#include <any>
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<ExposedVar> 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<std::string, Coral::Type*> m_GameEntityTypes;
std::unordered_map<std::string, NetGameScriptObject> m_GameEntityTypes;
std::unordered_map<uint32_t, Coral::ManagedObject> m_EntityToManagedObjects;
ScriptingEngineNet();
@@ -51,6 +77,10 @@ namespace Nuake {
void Initialize();
void Uninitialize();
bool IsInitialized() const { return m_IsInitialized; }
std::vector<ExposedVar> GetExposedVarForTypes(Entity entity);
void BuildProjectAssembly(Ref<Project> project);
void LoadProjectAssembly(Ref<Project> project);

View File

@@ -12,7 +12,6 @@ namespace Nuake.Net
/// This is the core Nuake.Net API.
/// All internal call should happen in this file.
/// </summary>
public class Engine
{
internal static unsafe delegate*<NativeString, void> LoggerLogIcall;
@@ -29,6 +28,12 @@ namespace Nuake.Net
}
}
[AttributeUsage(AttributeTargets.Field)]
public sealed class ExposedAttribute : Attribute
{
}
public class Debug
{
internal static unsafe delegate*</* start */ float, float, float,
@@ -47,6 +52,7 @@ namespace Nuake.Net
}
}
}