Skip to content

Add Copy, Paste As New, and Paste values for Components - #316

Open
Acissathar wants to merge 4 commits into
ProwlEngine:mainfrom
Acissathar:Add-copy-and-paste-to-components-in-Editor
Open

Add Copy, Paste As New, and Paste values for Components#316
Acissathar wants to merge 4 commits into
ProwlEngine:mainfrom
Acissathar:Add-copy-and-paste-to-components-in-Editor

Conversation

@Acissathar

Copy link
Copy Markdown
Contributor

Summary

This adds a component clipboard option in the component menu of the editor for Copy Component, Paste Component As New, and Paste Component Values inspired by other engines. The payload goes on the system clipboard as text so it can survive scene swaps and can copy from inside Prefab Edit mode to then exit and paste onto a scene object.

Example

CopyComponent

Notes

  • Scene references are resolved by identifier and not value as they survive the Prefab Edit mode serialize/reload, and helps prevent Echo from deep-cloning a GameObject/MB/Transform into a detached orphan.
  • Nothing pints the collectible script ALC since we just capture the string and EchoObject, so hot-reload should still unload as expected
  • Paste Values keeps target identity (identifier, GO, and sibling index)
  • Paste As New is disabled on Prefab Instances. Since Add Component button is disabled on a prefab instance (note I mean in scene view, not Prefab Edit), it made sense to not allow this as a work around to that since it seems to be an intentional design choice.
  • The non-english translation is done by AI. Since it's just a couple of words I feel like it's probably accurate, but I cannot personally verify.
  • DefaultInputHandler.Clipboard threw GlfwException when the OS clipboard held non-text content (image, file list, etc.) that in theory was already reachable, it was just a lot easier to hit with this change. Added some guards to help as there doesn't appear to be a format-query API and just blanket catching all exceptions felt bad. Maybe there is a better way to do this though, I'm not super familiar with Silk.

Limitations

  • If the ability to disallow multiple components of a single type were ever added (at least I couldn't find this functionality currently existing), then this may need a change to support not adding a new component - probably just disable paste as new like we do for prefab instances.
  • Reference preservation is top-level fields only. A scene ref nested inside a List<> or a custom serializable class still deep-clones.

@michaelsakharov

Copy link
Copy Markdown
Contributor

If im not mistaken, Echo has a DeserializeInto, so instead of manually doing everything, in theory you can simply do Serializer.Serialize(Component) and Serializer.DeserializeInto(Component) to copy all serializable fields over and also trigger Serialization Callbacks. That should be able to simplify a lot of this down a ton!

Could you look into that?

@Acissathar

Copy link
Copy Markdown
Contributor Author

That does make things easier, and also helps to hit a potential bug like with audio source where the fields are all private but are deserialized in its own Deserialize method. Also lets me drop the hashset and manually looping, and a few null guards since they're no longer possible to hit. Also cleaned up a few comments (and noted that audio source thing for future reference)

@michaelsakharov

Copy link
Copy Markdown
Contributor

Hmm though it seems your still doing additional work to work around a limitation in Echo?
I suppose references to other scene things would be serialized by echo and copies would be made rather then real references.

Give me a bit to see if I can come up with a nice solution inside Echo (Some kind of Reference Resolver)
Then we can simplify this code a ton!

@Acissathar

Copy link
Copy Markdown
Contributor Author

Right, currently (from my understanding at least) Echo clones out-of-graph references by value, so I believe the only way to shrink it would be by having some kind of "identifier-aware reference" in Echo so that we don't have to effectively replicate that here. Granted my experience with serialization is largely just custom unity editor's, so the guts of it I'm not 100% on (I could be making false assumptions fwiw)

But that said, I think that if Echo were to have that, then that also allows for that limitation above to potentially be resolved "for free" with getting to hit fields beyond the top level.

@michaelsakharov

Copy link
Copy Markdown
Contributor

Thats pretty much my plan, Im gonna add something like:

public interface IExternalReferenceResolver
{
object? GetReferenceKey(object value);
object? ResolveReference(object key, Type targetType);
}

That you can implement, to let you reference out-of-graph things rather then cloning them.
Currently we do have a way to achieve this via SerializationContext and OnSerialize OnDeserialize callbacks. We actually do something similiar for Assets, but a nicer first class solution inside Echo for this specific situation would be nice, as theres a few other cases I can think of where it would be useful. I'll push an update soon after some testing!

@michaelsakharov

Copy link
Copy Markdown
Contributor

Alright, just pushed it all and nuget is live so the main branch of the editor has the new version of Echo.

Also Scene.FindObjectByIdentifier is a way to find a Gameobject or Component via a persistence ID, so thats a good way to implement the IExternalReferenceResolved I think.

Heres what I added:

/// <summary>
/// Lets a caller mark object references that live outside the graph being serialized so Echo links
/// to them by a stable key instead of inlining (and, on deserialize, deep-copying) them.
/// The classic case is copy/paste of a scene object: references to other objects in the selection
/// should be duplicated, but references pointing out of the selection should resolve back to the
/// original live instances rather than spawning copies.
/// Set one on <see cref="SerializationContext.ExternalReferences"/>.
/// </summary>
public interface IExternalReferenceResolver
{
    /// <summary>
    /// Called during serialization for every reference-type value. Return a stable key when
    /// <paramref name="value"/> is external and should be linked rather than serialized; return null
    /// to serialize it normally. Keys should be simple, self-contained values (a Guid, string, int,
    /// or small struct) so they round-trip on their own.
    /// </summary>
    object? GetReferenceKey(object value);

    /// <summary>
    /// Called during deserialization to turn a key produced by <see cref="GetReferenceKey"/> back into
    /// the live instance. <paramref name="targetType"/> is the reference's declared/serialized type.
    /// Return null if the reference can no longer be resolved.
    /// </summary>
    object? ResolveReference(object key, Type targetType);
}

Inside SerializationContext.cs as a new field you can pass in:

public IExternalReferenceResolver? ExternalReferences;

That should be able to fix the out-of-graph cloning and allow you to significantly simplify the code!

@michaelsakharov

Copy link
Copy Markdown
Contributor

Ah sorry, I made a mistake I'll have it fixed and in main branch on prowl in a moment

@Acissathar

Copy link
Copy Markdown
Contributor Author

No worries, I'm in no rush!

@michaelsakharov

Copy link
Copy Markdown
Contributor

Alright should all be live now on main branch! sorry about that.

commit 654aaf6
Merge: 1834a31 6ba843b
Author: Wulferis <acer1171090@gmail.com>
Date:   Sat Jul 25 02:32:36 2026 +1000

    Merge branch 'main' of https://github.com/ProwlEngine/Prowl

commit 1834a31
Author: Wulferis <acer1171090@gmail.com>
Date:   Sat Jul 25 02:32:32 2026 +1000

    Updated prowl packages to v2.8.5

commit 6ba843b
Merge: 06e9525 07f61cf
Author: Wulferis <acer1171090@gmail.com>
Date:   Sat Jul 25 01:14:14 2026 +1000

    Merge pull request ProwlEngine#322 from Acissathar/fix-show-in-explorer-relative-path-mismatching-slashes

    Normalize separators in Show in Explorer

commit 07f61cf
Author: Will B <willbentz92@outlook.com>
Date:   Fri Jul 24 09:02:15 2026 -0600

    Normalize separators in Show in Explorer

commit 06e9525
Merge: 7cbdf72 17e7597
Author: Wulferis <acer1171090@gmail.com>
Date:   Fri Jul 24 19:39:47 2026 +1000

    Merge pull request ProwlEngine#318 from Acissathar/fix-collider-gizmo-offset-incorrect-rotation

    Fix collider DrawGizmo incorrecly placed the shape's Center offset through the combined rotation, leading to mismatch between actual physics shape and debugging

commit 7cbdf72
Merge: a5b09e4 ab9073a
Author: Wulferis <acer1171090@gmail.com>
Date:   Fri Jul 24 15:16:39 2026 +1000

    Merge pull request ProwlEngine#314 from PaperPrototype/empty-vertices-mesh-data

    Add error message for "Vertices data must not be empty when assigning vertex data"

commit a5b09e4
Merge: d0ea15b 6794e5b
Author: Kai Angulo <116330012+sinnwrig@users.noreply.github.com>
Date:   Thu Jul 23 21:30:00 2026 -0700

    Merge pull request ProwlEngine#317 from PaperPrototype/fix-release-runner

    Update launch.json

commit d0ea15b
Author: Wulferis <acer1171090@gmail.com>
Date:   Fri Jul 24 14:10:22 2026 +1000

    Updated prowl packages to v2.8.2

commit 3fb5f74
Author: Wulferis <acer1171090@gmail.com>
Date:   Fri Jul 24 13:31:57 2026 +1000

    More robust type resolving and scene deserialization

commit b96156c
Author: Wulferis <acer1171090@gmail.com>
Date:   Fri Jul 24 13:31:24 2026 +1000

    Updated all prowl packages to v2.8.1

commit 17e7597
Author: Will B <willbentz92@outlook.com>
Date:   Thu Jul 23 15:32:30 2026 -0600

    Fix collider DrawGizmo incorrecly placed the shape's Center offset through the combined rotation, leading to mismatch between actual physics shape and debugging

commit 6794e5b
Author: Abdiel Lopez <48071553+PaperPrototype@users.noreply.github.com>
Date:   Thu Jul 23 16:46:03 2026 -0400

    Update launch.json

commit ab9073a
Author: Abdiel Lopez <48071553+PaperPrototype@users.noreply.github.com>
Date:   Thu Jul 23 02:48:10 2026 -0400

    Update Mesh.cs
@Acissathar

Copy link
Copy Markdown
Contributor Author

This does help shrink it down, but we may be able to shrink it a little more. A couple of notes before I push up any changes to hear your thoughts on:

  1. We could move the SceneReferenceResolver into Prowl.Runtime as a shared API. Copy/paste of a scene object needs to link references to other scene objects by their persistence Guid instead of deep-cloning them which Echo supports thanks to your above change but the implementation is all Prowl knowledge. In theory this gives the benefit of extending the use from just the component clipboard into the GameObject/prefab duplication, because it does a similar thing, so if we make the SceneReferenceResolve public (constructed with the selection being copied), we can use it for both implementations and give a single place to get scene-reference.

  2. Skipping the external-reference check for the root value in Echo. Serializer.Serialize calls GetReferenceKey on the top-level (in addition to the nested references) to have every resolver special-case the root. If it returns a key for the object being serialized, Echo gives us a reference stub instead of the object data so we have to carry the "which object am I copying" selection, but now with this Echo change, the thing we are explicitly passing to Serialize should, by definition, be in-graph so we could skip that root value check, letting the resolves become stateless (scene resolver could become a singleton with no selection plumbing). Granted this is just more a general kind of improvement, and could easily be out of scope / not desired.

As is though, we're cutting approx 100 lines (granted a chunk is comments), but it is leaner!

@michaelsakharov

Copy link
Copy Markdown
Contributor
  1. Absolutely makes sense, Theres other cases where copy and paste would be used, so it makes sense to make that implementation shared, However I would put it inside the Editor rather then the Runtime I think.

  2. This one I disagree, This is under the assumption that what your serializing your trying to copy/paste, but Echo is a lot more general then just copy/paste, If I used IExternalReferenceResolver for an asset system for example, I may serialize a root Asset, but still want the root to know its an asset reference and not serialize its internals. Its entirely up to the host how the root is handled.

@Acissathar

Copy link
Copy Markdown
Contributor Author

Totally understandable, that's why I wanted to check first so no worries. I will give it a spin and see about putting them in Editor, thanks!

@Acissathar

Copy link
Copy Markdown
Contributor Author

Moved the scene reference resolver to its own class in the Editor Core, which let us chop out even more lines. I didn't include moving over the GameObject/Prefab duplication to this yet, I figured that might be better as its own PR for clarity? I don't mind including it though if you'd prefer it just be as one batch.

@michaelsakharov

Copy link
Copy Markdown
Contributor

I think this will become even smaller soon! The Undo code you have is accounting for assembly unloading.
But the new Hot Reload branch removes the idea of Unloading an assembly entirely. letting us simplify even more there!

We'll hold onto this PR for when Hotreload is merged in, This PR so far is good as is, if some wants to use it I think its pretty much perfect for main branch as is. I just dont want to merge just yet because of the change to Assembly Loading incoming.

@Acissathar

Copy link
Copy Markdown
Contributor Author

Sounds good to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants