mirror of
https://github.com/antopilo/Nuake.git
synced 2026-09-15 20:08:54 +03:00
Added basic C# field reflection in editor
This commit is contained in:
@@ -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(), ¤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, "");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user