Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d5eb55b
feat(editor): add dedicated .ovprefab file type and icon support
Gopmyc Apr 23, 2026
dd27e04
feat(editor): add prefab save/instantiate workflow and retargeting
Gopmyc Apr 23, 2026
64e7e4e
feat(prefab): track source instances and add apply/revert actions
Gopmyc Apr 23, 2026
c813f15
feat(editor): add prefab creation entry and hierarchy prefab controls
Gopmyc Apr 23, 2026
e045801
feat(icon) : Improve `Prefab` icon
Gopmyc Apr 23, 2026
6914c1b
Fix prefab context actions and nested prefab source remapping
Gopmyc Apr 24, 2026
3fc9291
Harden hierarchy prefab menu target resolution
Gopmyc Apr 24, 2026
06cb33e
Fix nested prefab source normalization and root resolution
Gopmyc Apr 24, 2026
d31b29f
Inline prefab instantiation guard logging on early returns
Gopmyc Apr 24, 2026
8823405
Inline prefab guard log-and-return in editor actions
Gopmyc Apr 24, 2026
655c1fb
Adjust prefab open behavior and default save filename
Gopmyc Apr 24, 2026
65361e3
Improve prefab instance workflows and actor-to-folder prefab drop
Gopmyc Apr 24, 2026
9b0f508
Refine prefab instantiation guard logging and compact single-line ifs
Gopmyc Apr 24, 2026
e8aa20c
Fix MSVC PDB contention by enabling /FS in premake
Gopmyc Apr 24, 2026
6e47f8d
fix(editor): keep prefab instance identity when reverting
Gopmyc Apr 24, 2026
fb1ee23
refactor(core): move prefab operations out of editor module
Gopmyc Apr 24, 2026
d05bfd1
fix(editor): restore contextual actions after scene reload
Gopmyc Apr 24, 2026
49d739d
feat(core): add prefab node GUID metadata for actor instances
Gopmyc Apr 30, 2026
50dcef4
refactor(editor): simplify hierarchy context target and actor creatio…
Gopmyc Apr 30, 2026
faa4b69
fix(editor): match prefab revert nodes by prefab GUID metadata
Gopmyc Apr 30, 2026
f253e46
fix(prefab): remap revert actor refs with serialized prefab GUIDs
Gopmyc Apr 30, 2026
5357328
fix(editor): guard context actor pointers in hierarchy menus
Gopmyc Apr 30, 2026
c953e90
chore(editor): remove non-essential prefab apply/revert info logs
Gopmyc Apr 30, 2026
4f4b606
Merge remote-tracking branch 'origin/main' into 25-beta
Gopmyc May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Resources/Editor/Textures/Prefab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions Sources/OvCore/include/OvCore/ECS/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,41 @@ namespace OvCore::ECS
*/
uint64_t GetGUID() const;

/**
* Defines the prefab source path for this actor.
* An empty path means this actor is not linked to a prefab source.
* @param p_prefabSource
*/
void SetPrefabSource(const std::string& p_prefabSource);

/**
* Returns the prefab source path for this actor.
*/
const std::string& GetPrefabSource() const;

/**
* Returns true if this actor is linked to a prefab source.
*/
bool HasPrefabSource() const;

/**
* Defines the prefab node GUID for this actor.
* This GUID identifies which prefab node this actor instance comes from.
* @param p_prefabNodeGUID
*/
void SetPrefabNodeGUID(uint64_t p_prefabNodeGUID);

/**
* Returns the prefab node GUID for this actor.
* A value of 0 means no prefab node GUID is assigned.
*/
uint64_t GetPrefabNodeGUID() const;

/**
* Returns true if this actor has a prefab node GUID assigned.
*/
bool HasPrefabNodeGUID() const;

/**
* Set an actor as the parent of this actor
* @param p_parent
Expand Down Expand Up @@ -369,6 +404,8 @@ namespace OvCore::ECS
/* Internal settings */
int64_t m_actorID;
uint64_t m_guid;
std::string m_prefabSource;
uint64_t m_prefabNodeGUID = 0;
bool m_destroyed = false;
bool m_sleeping = true;
bool m_awaked = false;
Expand Down
47 changes: 47 additions & 0 deletions Sources/OvCore/include/OvCore/SceneSystem/PrefabOperations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @project: Overload
* @author: Overload Tech.
* @licence: MIT
*/

#pragma once

#include <filesystem>
#include <functional>

namespace OvCore::ECS
{
class Actor;
}

namespace OvCore::SceneSystem
{
/**
* Utility functions to serialize and instantiate prefab files.
*/
class PrefabOperations
Comment thread
Gopmyc marked this conversation as resolved.
{
public:
/**
* Disabled constructor
*/
PrefabOperations() = delete;

/**
* Save an actor hierarchy to a prefab file.
* @param p_rootActor
* @param p_outputPath
*/
static bool SaveToFile(OvCore::ECS::Actor& p_rootActor, const std::filesystem::path& p_outputPath);

/**
* Instantiate a prefab file using the provided actor factory.
* @param p_prefabPath
* @param p_createActor
*/
static OvCore::ECS::Actor* InstantiateFromFile(
const std::filesystem::path& p_prefabPath,
const std::function<OvCore::ECS::Actor&(void)>& p_createActor
);
};
}
45 changes: 45 additions & 0 deletions Sources/OvCore/src/OvCore/ECS/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,43 @@ uint64_t OvCore::ECS::Actor::GetGUID() const
return m_guid;
}

void OvCore::ECS::Actor::SetPrefabSource(const std::string& p_prefabSource)
{
if (p_prefabSource == "?")
{
m_prefabSource.clear();
}
else
{
m_prefabSource = p_prefabSource;
}
}

const std::string& OvCore::ECS::Actor::GetPrefabSource() const
{
return m_prefabSource;
}

bool OvCore::ECS::Actor::HasPrefabSource() const
{
return !m_prefabSource.empty();
}

void OvCore::ECS::Actor::SetPrefabNodeGUID(uint64_t p_prefabNodeGUID)
{
m_prefabNodeGUID = p_prefabNodeGUID;
}

uint64_t OvCore::ECS::Actor::GetPrefabNodeGUID() const
{
return m_prefabNodeGUID;
}

bool OvCore::ECS::Actor::HasPrefabNodeGUID() const
{
return m_prefabNodeGUID != 0;
}

void OvCore::ECS::Actor::SetParent(Actor& p_parent)
{
DetachFromParent();
Expand Down Expand Up @@ -451,6 +488,8 @@ void OvCore::ECS::Actor::OnSerialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XM
OvCore::Helpers::Serializer::SerializeBoolean(p_doc, actorNode, "active", m_active);
OvCore::Helpers::Serializer::SerializeInt64(p_doc, actorNode, "id", m_actorID);
OvCore::Helpers::Serializer::SerializeUInt64(p_doc, actorNode, "guid", m_guid);
OvCore::Helpers::Serializer::SerializeString(p_doc, actorNode, "prefab_source", m_prefabSource);
OvCore::Helpers::Serializer::SerializeUInt64(p_doc, actorNode, "prefab_node_guid", m_prefabNodeGUID);
OvCore::Helpers::Serializer::SerializeInt64(p_doc, actorNode, "parent", m_parentID);

tinyxml2::XMLNode* componentsNode = p_doc.NewElement("components");
Expand Down Expand Up @@ -505,6 +544,12 @@ void OvCore::ECS::Actor::OnDeserialize(tinyxml2::XMLDocument & p_doc, tinyxml2::
OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_actorsRoot, "active", m_active);
OvCore::Helpers::Serializer::DeserializeInt64(p_doc, p_actorsRoot, "id", m_actorID);
OvCore::Helpers::Serializer::DeserializeUInt64(p_doc, p_actorsRoot, "guid", m_guid);
std::string prefabSource;
OvCore::Helpers::Serializer::DeserializeString(p_doc, p_actorsRoot, "prefab_source", prefabSource);
SetPrefabSource(prefabSource);
uint64_t prefabNodeGUID = 0;
OvCore::Helpers::Serializer::DeserializeUInt64(p_doc, p_actorsRoot, "prefab_node_guid", prefabNodeGUID);
SetPrefabNodeGUID(prefabNodeGUID);
OvCore::Helpers::Serializer::DeserializeInt64(p_doc, p_actorsRoot, "parent", m_parentID);

{
Expand Down
1 change: 1 addition & 0 deletions Sources/OvCore/src/OvCore/Helpers/GUIHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace
case EFileType::SOUND: return "Pick Sound";
case EFileType::SCRIPT: return "Pick Script";
case EFileType::SCENE: return "Pick Scene";
case EFileType::PREFAB: return "Pick Prefab";
default: return "Pick Asset";
}
}
Expand Down
132 changes: 132 additions & 0 deletions Sources/OvCore/src/OvCore/SceneSystem/PrefabOperations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @project: Overload
* @author: Overload Tech.
* @licence: MIT
*/

#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

#include <tinyxml2.h>

#include <OvDebug/Logger.h>
#include <OvCore/ECS/Actor.h>
#include <OvCore/SceneSystem/PrefabOperations.h>

namespace
{
void SerializeActorHierarchy(
OvCore::ECS::Actor& p_actor,
tinyxml2::XMLDocument& p_doc,
tinyxml2::XMLNode& p_actorsRoot)
{
if (!p_actor.HasPrefabNodeGUID())
{
p_actor.SetPrefabNodeGUID(p_actor.GetGUID());
}

p_actor.OnSerialize(p_doc, &p_actorsRoot);

for (auto* child : p_actor.GetChildren())
{
SerializeActorHierarchy(*child, p_doc, p_actorsRoot);
}
}
}

bool OvCore::SceneSystem::PrefabOperations::SaveToFile(OvCore::ECS::Actor& p_rootActor, const std::filesystem::path& p_outputPath)
{
tinyxml2::XMLDocument doc;

auto* rootNode = doc.NewElement("root");
doc.InsertFirstChild(rootNode);

auto* prefabNode = doc.NewElement("prefab");
rootNode->InsertEndChild(prefabNode);

auto* actorsNode = doc.NewElement("actors");
prefabNode->InsertEndChild(actorsNode);

SerializeActorHierarchy(p_rootActor, doc, *actorsNode);

return doc.SaveFile(p_outputPath.string().c_str()) == tinyxml2::XML_SUCCESS;
}

OvCore::ECS::Actor* OvCore::SceneSystem::PrefabOperations::InstantiateFromFile(
const std::filesystem::path& p_prefabPath,
const std::function<OvCore::ECS::Actor&(void)>& p_createActor)
{
if (!p_createActor)
{
OVLOG_ERROR("Failed to instantiate prefab \"" + p_prefabPath.string() + "\": invalid actor factory callback.");
return nullptr;
}

tinyxml2::XMLDocument doc;
const auto loadResult = doc.LoadFile(p_prefabPath.string().c_str());
if (loadResult != tinyxml2::XML_SUCCESS)
{
OVLOG_ERROR("Failed to instantiate prefab \"" + p_prefabPath.string() + "\": XML parsing failed (code " + std::to_string(loadResult) + ").");
return nullptr;
}

auto* rootNode = doc.FirstChildElement("root");
auto* prefabNode = rootNode ? rootNode->FirstChildElement("prefab") : nullptr;
auto* actorsNode = prefabNode ? prefabNode->FirstChildElement("actors") : nullptr;

if (!actorsNode)
{
OVLOG_ERROR("Failed to instantiate prefab \"" + p_prefabPath.string() + "\": missing <root>/<prefab>/<actors> node.");
return nullptr;
}

struct PendingAttachment
{
OvCore::ECS::Actor* actor = nullptr;
int64_t sourceParentID = 0;
};

std::vector<PendingAttachment> pendingAttachments;
std::unordered_map<int64_t, OvCore::ECS::Actor*> sourceToInstance;

for (auto* currentActor = actorsNode->FirstChildElement("actor");
currentActor;
currentActor = currentActor->NextSiblingElement("actor"))
{
auto& newActor = p_createActor();
const int64_t generatedID = newActor.GetID();
const uint64_t generatedGUID = newActor.GetGUID();

newActor.OnDeserialize(doc, currentActor);
const uint64_t prefabNodeGUID = newActor.HasPrefabNodeGUID() ? newActor.GetPrefabNodeGUID() : newActor.GetGUID();
newActor.SetPrefabNodeGUID(prefabNodeGUID);

pendingAttachments.push_back({
&newActor,
newActor.GetParentID()
});

sourceToInstance[newActor.GetID()] = &newActor;

newActor.SetID(generatedID);
newActor.SetGUID(generatedGUID);
}

OvCore::ECS::Actor* instantiatedRoot = nullptr;

for (auto& pending : pendingAttachments)
{
if (auto found = sourceToInstance.find(pending.sourceParentID); found != sourceToInstance.end())
{
pending.actor->SetParent(*found->second);
}
else if (!instantiatedRoot)
{
instantiatedRoot = pending.actor;
}
}

return instantiatedRoot;
}
28 changes: 28 additions & 0 deletions Sources/OvEditor/include/OvEditor/Core/EditorActions.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,34 @@ namespace OvEditor::Core
bool p_focus = true,
bool p_keepSourceParentIfNoForcedParent = true
);

/**
* Save an actor hierarchy to a prefab file
* @param p_actor
* @param p_path
*/
void SaveActorAsPrefab(OvCore::ECS::Actor& p_actor, const std::string& p_path);

/**
* Instantiate a prefab file in the current scene
* @param p_path
*/
OvCore::ECS::Actor* InstantiatePrefab(const std::string& p_path);

/**
* Apply the current actor hierarchy state to its prefab source.
* Returns true on success.
* @param p_actor
*/
bool ApplyActorToPrefab(OvCore::ECS::Actor& p_actor);

/**
* Revert an actor hierarchy from its prefab source.
* Keeps existing actor GUIDs to preserve scene/script references.
* Returns true on success.
* @param p_actor
*/
bool RevertActorToPrefab(OvCore::ECS::Actor& p_actor);
#pragma endregion

#pragma region ACTOR_MANIPULATION
Expand Down
3 changes: 2 additions & 1 deletion Sources/OvEditor/include/OvEditor/Panels/SceneView.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ namespace OvEditor::Panels
void OnSceneDropped(const std::string& p_path);
void OnModelDropped(const std::string& p_path);
void OnMaterialDropped(const std::string& p_path);
void OnPrefabDropped(const std::string& p_path);

private:
OvCore::SceneSystem::SceneManager& m_sceneManager;
Expand All @@ -74,4 +75,4 @@ namespace OvEditor::Panels
OvTools::Utils::OptRef<OvCore::ECS::Actor> m_highlightedActor;
std::optional<OvEditor::Core::GizmoBehaviour::EDirection> m_highlightedGizmoDirection;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma once

#include <functional>
#include <optional>

namespace OvUI::Widgets::Menu
{
Expand Down
Loading