Started editor .Net context for undo/redo system

This commit is contained in:
Antoine Pilote
2024-08-25 22:42:07 -04:00
parent 03231e6bf7
commit 9a30925b34
18 changed files with 531 additions and 28 deletions

View File

@@ -0,0 +1,7 @@
#include "CommandManager.h"
#include "../ScriptingContext/ScriptingContext.h"
void CommandManager::Push(const std::string & command, const std::string & value)
{
//riptingContext::Get().PushCommand(command, args);
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <string>
class CommandManager
{
public:
static CommandManager& Get()
{
static CommandManager instance;
return instance;
}
void Push(const std::string& command, const std::string& value);
};

View File

@@ -0,0 +1,24 @@
#include "EditorModule.h"
#include "../../Windows/EditorInterface.h"
void SelectEntity(int entityId)
{
if (entityId == -1)
{
Nuake::EditorInterface::Selection = EditorSelection();
return;
}
//Nuake::EditorInterface::Selection = EditorSelection(Nuake::Entity{ (entt::entity)entityId, Nuake::Engine::GetCurrentScene().get() });
}
int GetSelectedEntity()
{
return -10;
}
void EditorNetAPI::RegisterMethods()
{
RegisterMethod("Editor.SetSelectedEntityIcall", &SelectEntity);
RegisterMethod("Editor.GetSelectedEntityIcall", &GetSelectedEntity);
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "src/Scripting/NetModules/NetAPIModule.h"
class EditorNetAPI : public Nuake::NetAPIModule
{
public:
virtual const std::string GetModuleName() const override { return "Editor"; }
virtual void RegisterMethods() override;
};

View File

@@ -0,0 +1,39 @@
#include "ScriptingContext.h"
#include "src/Core/String.h"
#include "Modules/EditorModule.h"
void ScriptingContext::Initialize()
{
m_Modules =
{
CreateRef<EditorNetAPI>()
};
for (auto& m : m_Modules)
{
m->RegisterMethods();
}
// Load Nuake assembly DLL
auto m_LoadContext2 = Nuake::ScriptingEngineNet::Get().GetHostInstance()->CreateAssemblyLoadContext("NuakeEditorContext");
m_NuakeAssembly = Nuake::ScriptingEngineNet::Get().ReloadEngineAPI(m_LoadContext2);
m_EditorAssembly = m_LoadContext2.LoadAssembly("EditorNet.dll");
for (const auto& netModule : m_Modules)
{
for (const auto& [methodName, methodPtr] : netModule->GetMethods())
{
auto namespaceClassSplit = Nuake::String::Split(methodName, '.');
m_EditorAssembly.AddInternalCall("Nuake." + namespaceClassSplit[0], namespaceClassSplit[1], methodPtr);
}
}
m_EditorAssembly.UploadInternalCalls();
m_EditorAssembly.GetType("Nuake.Editor").InvokeStaticMethod("Initialize");
}
void ScriptingContext::PushCommand(const std::string & command, const std::string & arg)
{
m_EditorAssembly.GetType("Nuake.Editor").InvokeStaticMethod("Initialize");
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "src/Scripting/NetModules/NetAPIModule.h"
namespace Coral
{
class HostInstance;
class AssemblyLoadContext;
class ManagedAssembly;
}
#include "src/Scripting/ScriptingEngineNet.h"
class ScriptingContext
{
private:
Coral::HostInstance* m_HostInstance;
Coral::AssemblyLoadContext m_LoadContext;
std::unordered_map<std::string, Coral::AssemblyLoadContext*> m_LoadedAssemblies;
std::vector<Ref<Nuake::NetAPIModule>> m_Modules;
Coral::ManagedAssembly m_NuakeAssembly; // Nuake DLL
Coral::ManagedAssembly m_EditorAssembly; // Editor DLL
public:
static ScriptingContext& Get()
{
static ScriptingContext instance;
return instance;
}
void Initialize();
void PushCommand(const std::string& command, const std::string& arg);
};

View File

@@ -54,6 +54,7 @@
#include <src/Threading/JobSystem.h>
#include "../Commands/Commands/Commands.h"
#include <src/Resource/ModelLoader.h>
#include "../ScriptingContext/ScriptingContext.h"
namespace Nuake {
@@ -62,6 +63,7 @@ namespace Nuake {
ImFont* EditorInterface::bigIconFont;
NuakeEditor::CommandBuffer* EditorInterface::mCommandBuffer;
EditorSelection EditorInterface::Selection;
glm::vec3 DepthToWorldPosition(const glm::vec2& pixelPos, float depth, const glm::mat4& projectionMatrix, const glm::mat4& viewMatrix, const glm::vec2& viewportSize)
{
@@ -112,6 +114,8 @@ namespace Nuake {
Logger::Log("Loading imgui from mem", "window", VERBOSE);
using namespace Nuake::StaticResources;
ImGui::LoadIniSettingsFromMemory((const char*)StaticResources::Resources_default_layout_ini);
ScriptingContext::Get().Initialize();
}
void EditorInterface::Init()

View File

@@ -62,7 +62,7 @@ namespace Nuake
WelcomeWindow* _WelcomeWindow;
AudioWindow* _audioWindow;
FileSystemUI* filesystem;
EditorSelection Selection;
static EditorSelection Selection;
EditorSelectionPanel SelectionPanel;
TrenchbroomConfiguratorWindow m_TrenchhbroomConfigurator;
MapImporterWindow m_MapImporter;

32
EditorNet/premake5.lua Normal file
View File

@@ -0,0 +1,32 @@
project "EditorNet"
language "C#"
dotnetframework "net8.0"
kind "SharedLib"
clr "Unsafe"
-- Don't specify architecture here. (see https://github.com/premake/premake-core/issues/1758)
propertytags {
{ "AppendTargetFrameworkToOutputPath", "false" },
{ "Nullable", "enable" },
}
files
{
"src/**.cs"
}
links
{
"Coral.Managed",
"../Editor/NuakeNet.dll"
}
prebuildcommands {
-- "dotnet add package Microsoft.CodeAnalysis.CSharp.Scripting"
}
postbuildcommands {
'{ECHO} Copying "%{wks.location}/EditorNet/bin/$(Configuration)/EditorNet.dll" to "%{wks.location}/Editor"',
'{COPYFILE} "%{wks.location}/EditorNet/bin/$(Configuration)/EditorNet.dll" "%{wks.location}/Editor"'
}

261
EditorNet/src/Editor.cs Normal file
View File

@@ -0,0 +1,261 @@
using Nuake.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Nuake
{
class Editor
{
internal static unsafe delegate*<int, void> SetSelectedEntityIcall;
internal static unsafe delegate*<int> GetSelectedEntityIcall;
static int STACK_CAPACITY = 32;
private static Stack<ICommand> CommandStack = new(STACK_CAPACITY);
public static int SelectedEntity
{
get
{
unsafe
{
return GetSelectedEntityIcall();
}
}
set
{
unsafe
{
SetSelectedEntityIcall(value);
}
}
}
static void Initialize()
{
RegisterCommand();
}
static void RegisterCommand()
{
SelectEntity testCommand = new SelectEntity(10);
testCommand.Do();
testCommand.Undo();
SetEntityFieldProperty test = new SetEntityFieldProperty(1, "LightComponent.Intensity", "10");
test.Do();
test.Undo();
test.Do();
}
public static void PushCommand(String command, String value)
{
}
public static void PushCommand(ICommand command)
{
CommandStack.Push(command);
command.Do();
}
static void Undo()
{
}
static void Redo()
{
}
}
namespace Commands
{
interface ICommand
{
abstract string GetName();
public abstract void Do();
public virtual bool Undo() { return false; }
}
class SetEntityFieldProperty : ICommand
{
private object Instance;
private object PropertyValue;
private object PropertyOldValue;
private PropertyInfo PropertyInfo;
public SetEntityFieldProperty(int id, string name, string value)
{
// Parse the input string into component name and field name
string[] parts = name.Split('.');
if (parts.Length != 2)
{
throw new ArgumentException("The input format should be 'ComponentName.FieldName'");
}
string componentName = parts[0];
string fieldName = parts[1];
// Load the Nuake.Net assembly
Assembly nuakeAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "NuakeNet");
if (nuakeAssembly == null)
{
throw new InvalidOperationException("Nuake.Net assembly not found.");
}
// Find the component class within the specified namespace that matches the component name
var componentType = nuakeAssembly.GetTypes()
.FirstOrDefault(t => t.Name.Equals(componentName, StringComparison.OrdinalIgnoreCase) && t.IsClass);
if (componentType == null)
{
Nuake.Net.Engine.Log($"Component '{componentName}' not found in the Nuake.Net assembly.");
}
// Find the field within the component class
PropertyInfo = componentType.GetProperty(fieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (PropertyInfo == null)
{
Nuake.Net.Engine.Log(($"Field '{fieldName}' not found in component '{componentName}'."));
}
// Assume we have an instance of the component (you would need to pass the correct instance here)
Instance = Activator.CreateInstance(componentType, id); // Example: creating an instance for demonstration
// Convert the value to the correct type and set the property
object convertedValue = Convert.ChangeType(value, PropertyInfo.PropertyType);
PropertyOldValue = PropertyInfo.GetValue(Instance);
PropertyValue = convertedValue;
Nuake.Net.Engine.Log($"Set '{fieldName}' of '{componentName}' to '{value}' in Nuake.Net.");
}
public virtual void Do()
{
Nuake.Net.Engine.Log($"'{PropertyValue}' in Nuake.Net.");
PropertyInfo.SetValue(Instance, PropertyValue);
}
public string GetName()
{
return "SetFieldProperty";
}
public virtual bool Undo()
{
PropertyInfo.SetValue(Instance, PropertyOldValue);
Nuake.Net.Engine.Log($"'{PropertyOldValue}' in Nuake.Net.");
return true;
}
}
class SetStaticFieldProperty : ICommand
{
private object PropertyValue;
private object PropertyOldValue;
private PropertyInfo PropertyInfo;
public SetStaticFieldProperty(string name, string value)
{
// Parse the input string into component name and field name
string[] parts = name.Split('.');
if (parts.Length != 2)
{
throw new ArgumentException("The input format should be 'ComponentName.FieldName'");
}
string componentName = parts[0];
string fieldName = parts[1];
// Load the Nuake.Net assembly
Assembly nuakeAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "NuakeNet");
if (nuakeAssembly == null)
{
throw new InvalidOperationException("Nuake.Net assembly not found.");
}
// Find the component class within the specified namespace that matches the component name
var componentType = nuakeAssembly.GetTypes()
.FirstOrDefault(t => t.Name.Equals(componentName, StringComparison.OrdinalIgnoreCase) && t.IsClass);
if (componentType == null)
{
Nuake.Net.Engine.Log($"Component '{componentName}' not found in the Nuake.Net assembly.");
}
// Find the field within the component class
PropertyInfo = componentType.GetProperty(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (PropertyInfo == null)
{
Nuake.Net.Engine.Log(($"Static Field '{fieldName}' not found in component '{componentName}'."));
}
// Convert the value to the correct type and set the property
object convertedValue = Convert.ChangeType(value, PropertyInfo.PropertyType);
PropertyOldValue = PropertyInfo.GetValue(null);
PropertyValue = convertedValue;
Nuake.Net.Engine.Log($"Set '{fieldName}' of '{componentName}' to '{value}' in Nuake.Net.");
}
public virtual void Do()
{
PropertyInfo.SetValue(null, PropertyValue);
Nuake.Net.Engine.Log($"'{PropertyValue}' in Nuake.Net.");
}
public string GetName()
{
return "SetFieldProperty";
}
public virtual bool Undo()
{
PropertyInfo.SetValue(null, PropertyOldValue);
Nuake.Net.Engine.Log($"'{PropertyOldValue}' in Nuake.Net.");
return true;
}
}
class SelectEntity(int entityID) : ICommand
{
private readonly int EntityID = entityID;
private readonly int PreviousEntityID = Editor.SelectedEntity;
public void Do()
{
Editor.SelectedEntity = EntityID;
}
public bool Undo()
{
Editor.SelectedEntity = PreviousEntityID;
return true;
}
public string GetName()
{
return "SelectEntity";
}
}
}
}

View File

@@ -9,8 +9,10 @@
#include "src/Rendering/Renderer.h"
#include "src/Rendering/Renderer2D.h"
#include "src/Scripting/ScriptingEngine.h"
#include "src/Scripting/ScriptingEngineNet.h"
#include "src/Threading/JobSystem.h"
#include <imgui/imgui_impl_glfw.h>
#include <imgui/imgui_impl_opengl3.h>
@@ -139,7 +141,12 @@ namespace Nuake
Ref<Scene> Engine::GetCurrentScene()
{
return s_CurrentWindow->GetScene();
if (s_CurrentWindow)
{
return s_CurrentWindow->GetScene();
}
return nullptr;
}
bool Engine::LoadScene(Ref<Scene> scene)

View File

@@ -41,6 +41,7 @@ namespace Nuake
}
auto& scriptingEngineNet = ScriptingEngineNet::Get();
scriptingEngineNet.Uninitialize();
scriptingEngineNet.Initialize();
scriptingEngineNet.LoadProjectAssembly(Engine::GetProject());

View File

@@ -277,6 +277,18 @@ namespace Nuake {
}
}
float LightGetIntensity(int entityId)
{
Logger::Log("Get light intensity with id: " + std::to_string(entityId));
return -10.0f;
}
void LightSetIntensity(int entityId, float intensity)
{
Logger::Log("Set light intensity with id: " + std::to_string(intensity));
}
Coral::Array<float> CameraGetDirection(int entityId)
{
Entity entity = { (entt::entity)(entityId), Engine::GetCurrentScene().get() };
@@ -402,6 +414,10 @@ namespace Nuake {
RegisterMethod("TransformComponent.GetGlobalPositionIcall", &TransformGetGlobalPosition);
RegisterMethod("TransformComponent.RotateIcall", &TransformRotate);
// Lights
RegisterMethod("LightComponent.GetLightIntensityIcall", &LightGetIntensity);
RegisterMethod("LightComponent.SetLightIntensityIcall", &LightSetIntensity);
// Camera
RegisterMethod("CameraComponent.GetDirectionIcall", &CameraGetDirection);

View File

@@ -11,10 +11,7 @@
#include "NetModules/InputNetAPI.h"
#include "NetModules/SceneNetAPI.h"
#include <Coral/HostInstance.hpp>
#include <Coral/GC.hpp>
#include <Coral/Array.hpp>
#include <Coral/Attribute.hpp>
#include <random>
#include <regex>
@@ -60,6 +57,17 @@ namespace Nuake
{
m->RegisterMethods();
}
// Check if we have an .sln in the project.
const std::string absoluteAssemblyPath = FileSystem::Root + m_NetDirectory + "/" + m_EngineAssemblyName;
if (!FileSystem::FileExists(m_EngineAssemblyName, true))
{
m_IsInitialized = false;
return;
}
}
ScriptingEngineNet::~ScriptingEngineNet()
@@ -122,21 +130,9 @@ namespace Nuake
return instance;
}
void ScriptingEngineNet::Initialize()
Coral::ManagedAssembly ScriptingEngineNet::ReloadEngineAPI(Coral::AssemblyLoadContext& context)
{
// Check if we have an .sln in the project.
const std::string absoluteAssemblyPath = FileSystem::Root + m_NetDirectory + "/" + m_EngineAssemblyName;
if (!FileSystem::FileExists(m_EngineAssemblyName, true))
{
m_IsInitialized = false;
return;
}
m_LoadContext = m_HostInstance->CreateAssemblyLoadContext(m_ContextName);
// Load Nuake assembly DLL
m_NuakeAssembly = m_LoadContext.LoadAssembly(m_EngineAssemblyName);
auto assembly = context.LoadAssembly(m_EngineAssemblyName);
// Upload internal calls for each module
// --------------------------------------------------
@@ -145,11 +141,20 @@ namespace Nuake
for (const auto& [methodName, methodPtr] : netModule->GetMethods())
{
auto namespaceClassSplit = String::Split(methodName, '.');
m_NuakeAssembly.AddInternalCall(m_Scope + '.' + namespaceClassSplit[0], namespaceClassSplit[1], methodPtr);
assembly.AddInternalCall(m_Scope + '.' + namespaceClassSplit[0], namespaceClassSplit[1], methodPtr);
}
}
m_NuakeAssembly.UploadInternalCalls();
assembly.UploadInternalCalls();
return assembly;
}
void ScriptingEngineNet::Initialize()
{
m_LoadContext = m_HostInstance->CreateAssemblyLoadContext(m_ContextName);
m_NuakeAssembly = ReloadEngineAPI(m_LoadContext);
m_IsInitialized = true;
@@ -170,9 +175,8 @@ namespace Nuake
Coral::GC::Collect();
m_HostInstance->UnloadAssemblyLoadContext(m_LoadContext);
GetHostInstance()->UnloadAssemblyLoadContext(m_LoadContext);
m_EntityToManagedObjects.clear();
}

View File

@@ -11,6 +11,10 @@ namespace Coral
class Type;
}
#include <Coral/HostInstance.hpp>
#include <Coral/GC.hpp>
#include <Coral/Array.hpp>
#include <Coral/Attribute.hpp>
#include <Coral/Assembly.hpp>
namespace Nuake {
@@ -87,6 +91,11 @@ namespace Nuake {
public:
static ScriptingEngineNet& Get();
Coral::HostInstance* GetHostInstance() { return m_HostInstance; }
Coral::AssemblyLoadContext& GetLoadContext() { return m_LoadContext; }
Coral::ManagedAssembly ReloadEngineAPI(Coral::AssemblyLoadContext & context);
void Initialize();
void Uninitialize();
@@ -107,6 +116,8 @@ namespace Nuake {
std::string FindClassNameInScript(const std::string& filePath);
Coral::ManagedAssembly GetNuakeAssembly() const { return m_NuakeAssembly; }
private:
std::string GenerateGUID();
};

View File

@@ -113,6 +113,9 @@ namespace Nuake.Net
public class LightComponent : IComponent
{
internal static unsafe delegate*<int, float> GetLightIntensityIcall;
internal static unsafe delegate*<int, float, void> SetLightIntensityIcall;
public enum LightType
{
Directional,
@@ -126,7 +129,25 @@ namespace Nuake.Net
}
public LightType Type { get; set; }
public float Intensity { get; set; }
public float Intensity
{
get
{
unsafe
{
return GetLightIntensityIcall(EntityID);
}
}
set
{
unsafe
{
SetLightIntensityIcall(EntityID, value);
}
}
}
public Color Color { get; set; }
public bool CastShadows { get; set; }
public bool Volumetric { get; set; }

18
NuakeNet/src/Renderer.cs Normal file
View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nuake.Net
{
class Renderer
{
// Renderer settings
public bool VSync { get; set; }
public float ResolutionScale { get; set; }
}
}

View File

@@ -38,7 +38,7 @@ group "Dependencies"
group ""
include "NuakeNet/premake5.lua"
include "EditorNet/premake5.lua"
project "Nuake"
location "Nuake"
@@ -140,7 +140,6 @@ project "Nuake"
runtime "Release"
optimize "on"
project "NuakeRuntime"
location "Runtime"
kind "ConsoleApp"
@@ -270,7 +269,6 @@ project "NuakeRuntime"
"WIN32_LEAN_AND_MEAN"
}
project "Editor"
location "Editor"
kind "ConsoleApp"