We are using the Sandbox sample as basis for a prototype. We realized, that when you create a duplicate of a model from the side (e.g the cactus), that over time (for example by leaving the browser for a while and coming back) you can create a duplicate out of the duplicate.
Original = Object 0
Drag a copy âout ofâ object 0 â object 1
Place it in the scene
leave the browser for a while
now you can create a clone = object 2 âout ofâ object 1
It looks like youâre encountering an issue with object duplication in the Sandbox sample of Needle Engine. When you create a copy of an object and then leave the browser for a while, it seems that references to the original object might not be maintained correctly, allowing for the duplication of duplicates.
Here are some potential reasons for this behavior and how to prevent it:
Reasons:
State Management: When you leave the browser for an extended period, the state of objects might not be saved properly. This can lead to instances where the system doesnât recognize that there are already existing duplicates.
Event Listeners: If drag-and-drop operations arenât properly managed with event listeners, a duplicated object may be treated as a new original when manipulations happen after returning from inactivity.
Instance Tracking: The system may not be correctly tracking instances of objects, leading to misidentification of what constitutes a new object versus an existing clone.
Suggestions to Prevent Duplication:
Object Instance Tracking: Implement a unique ID for each created object. When an object is cloned, check if the object has already been duplicated. You can do this by maintaining a list of IDs in a central manager or service.
const objectManager = {
createdObjects: {}, // Keep track of created object IDs
createObject() {
// Generate new unique ID
const id = generateUniqueId();
this.createdObjects[id] = new Object(); // Replace with actual object creation logic
return id;
},
isDuplicate(id) {
return id in this.createdObjects;
},
};
Reinstate State on Return: Use browser session storage or a similar method to save the current state of your objects when the user leaves the site, and restore it when they return.
Enforce Cloning Logic: When a user tries to duplicate an object, check whether the source is already a duplicate. If it is, prevent additional duplicates from being created.
Example
Hereâs a pseudocode example of implementing some of the above suggestions in your drag-and-drop logic:
function onDragStart(object) {
// Track the object's ID or reference
}
function onDrop(newPosition) {
if (objectManager.isDuplicate(currentObjectId)) {
alert("Cannot create a duplicate of a duplicate object!");
return;
}
// Proceed with logic to drop the object
}
By tracking object instances and managing state effectively, you can likely prevent this duplication issue. If you need help with specific parts of your code, feel free to share!
1 replies left - Get a PRO license for more AI replies. thread_ntIc2cSxyXNuLTvsRVQOrXU4 Reply to this message to continue the conversation
Hello, it does reproduce and believe to know why it happens:
I think the second duplication happens because the networking room disconnects due to inactivity (e.g. when your browser pages is not visible anymore) and when you focus it again it reconnects and the object gets spawned again at the last known position (which happens to be the exact position of the duplicated object)
The problem is here that the object that is spawned with syncedInstantiate is not automatically deleted by the engine when the room disconnects.
You can make this issue even more apparent by using the Leave Room and Join Room menu button in the Needle Menu. Each time you re-join you will see one more instance of each object of the duplicated object.
I will create an issue to track and think about how to solve this properly.
Thanks for reporting!
As a workaround you could add a custom component to your objects that are duplicated with the Duplicatable component and listen to the LeftRoom networking event to destroy the object again
I added the following code to fix this problem for me now.
But I canât receive the event âRoomEvents.UserLeftRoomâ and my method âuserLeftRoomâ is never called.
Iâm very confused about your networking manual too.
In the chapter âNetworking Lifecycle Eventsâ you wrote I should add the listener like:
this.context.beginListen(RoomEvents.UserLeftRoom, ({userId}) => { ⊠});
But with âthis.context.beginListenâ i got errors I have to use âthis.context.connection.beginListenâ
And your manual didât explain where I get the âuserIdâ from too. So I had to guess. But I think for this fix I will not need the userid.
This is my code. What did I make wrong?
awake() {
// Listen to the event when another user has left your networked room
this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.userLeftRoom.bind(this));
}
Hi, youâre using the event for when another user is disconnecting from a networked room - the user id in the event callback is the ID of the other user.
Please use the RoomEvents.LeftRoom event.
// Listen to the event when *you* have left a networked room
this.context.beginListen(RoomEvents.LeftRoom, ({room}) => { ... });
For completeness: you can get your own user id from context.connection.connectionId - but you dont need that for this case
I have no idea anymore. I donât know how I should write a workaround for this bug.
When I get the âLeftRoomâ callback and like you wrote I destroy the object with âGameObject.destroyâ or with âsyncDestroyâ. It doesnât matters. I will get lot of these error messages:
âTransformControls: The attached 3D object must be a part of the scene graph.
could not find object that was instantiated: âŠâ
I donât know how to fix it without side effects?
That sounds like an issue with TransformControls and might not be related - could you share more info on the objects and your scene setup that youâre working with here? Because the Sandbox scene doesnât contain objects with TransformControls.
It would also be helpful to see the full stacktrace of the error.
Edit: I just tried to reproduce the error locally but could not, so more info about your scene is needed
My project is based on the Sandbox example. I made a script for it.
I call it âDragRotateâ. Its extends DragControls.
With allows me to move a object like with âDragControlsâ.
Then I combined it with the code from âTransformGizmoâ with is using TransformControls.
With this combination I have a script for object I can drag and when its selected it has a rotation gizmo active which allows me to rotate the object on the y axis.
For the Stacktrace i donât know how to add files here. So in short the TransformControls has some problems with the âAnimationFrameâ:
chunk-TNM5YZYQ.js?v=6aaa151c:139 TransformControls: The attached 3D object must be a part of the scene graph.
updateMatrixWorld @ chunk-TNM5YZYQ.js?v=6aaa151c:139
updateMatrixWorld @ chunk-VU7D7ALY.js?v=6aaa151c:5149
WebGLRenderer.render @ chunk-VU7D7ALY.js?v=6aaa151c:18349
wrappedFunction @ chunk-G2Z4LREE.js?v=6aaa151c:20831
renderer.render @ chunk-G2Z4LREE.js?v=6aaa151c:25759
wrappedFunction @ chunk-G2Z4LREE.js?v=6aaa151c:20831
renderNow @ chunk-G2Z4LREE.js?v=6aaa151c:41791
internalOnRender @ chunk-G2Z4LREE.js?v=6aaa151c:41739
internalStep @ chunk-G2Z4LREE.js?v=6aaa151c:41594
update @ chunk-G2Z4LREE.js?v=6aaa151c:41564
(anonymous) @ chunk-G2Z4LREE.js?v=6aaa151c:41553
onAnimationFrame @ chunk-VU7D7ALY.js?v=6aaa151c:18322
onAnimationFrame @ chunk-VU7D7ALY.js?v=6aaa151c:8669
requestAnimationFrame
onAnimationFrame @ chunk-VU7D7ALY.js?v=6aaa151c:8670
requestAnimationFrame
onAnimationFrame @ chunk-VU7D7ALY.js?v=6aaa151c:8670
requestAnimationFrame
onAnimationFrame @ chunk-VU7D7ALY.js?v=6aaa151c:8670
Here is the initialisation part for it. I have to say Iâm more a Unity C# programmer then typescript.
onEnable() {
super.onEnable();
// this is nearly the same like in TransformGizmo
if (!this.context.mainCamera) return;
if (!this.control) {
this.control = new TransformControls(this.context.mainCamera, this.context.renderer.domElement);
this.control.visible = true;
this.control.enabled = true;
this.control.getRaycaster().layers.set(2);
this.control.size = 1;
this.control.traverse(x => {
const mesh = x as Mesh;
mesh.layers.set(2);
if (mesh) {
const gizmoMat = mesh.material as MeshBasicMaterial;
if (gizmoMat) {
gizmoMat.opacity = 0.8;
}
}
});
this.orbit = GameObject.getComponentInParent(this.context.mainCamera, OrbitControls) ?? undefined;
}
if (this.control) {
this.context.scene.add(this.control);
this.control.setMode('rotate');
this.control.showX = false;
this.control.showY = false;
this.control.showZ = false;
}
}
The rest happens whe the object is clicked:
onPointerDown(args: PointerEventData) {
if (args.used) return;
if (args.button === 0) {
if (this.control) {
this.registerGizmo();
this.control.showY = true;
}
//args.use(); // Not used it will be called in super.onPointerDown(args);
}
super.onPointerDown(args);
Yes.
I remove all listeners onDisable() and when i have a mouseup from the Gizmo the gizmo should disapear after time then it will deregisterd too ( will added agian on object click)
But i remove the object only with
this.control?.remove(this.gameObject)
Not
this.control?.removeFromParent();
or
this.control.detach();
I could not reproduce this error with the Transform Gizmo in the bugreport you sent.
Please note that we just published alpha 4.0.1 which contains the synced instantiate fix as well as the screenshot fix for instanced objects.
While looking at your code I noticef that youâre not correctly subscribing to events in some places in your code (e.g. GameObjectExtension) which leads to a lot of functions being created and added to the list but never removed.
E.g. the code below in GameObjectExtension doesnt work because you create a new function here so and later try to unsubscribe from it with another new functionâŠ
awake() {
this.context.connection.beginListen(RoomEvents.LeftRoom, this.leftRoom);
}
onDestroy() {
this.context.connection.stopListen(RoomEvents.LeftRoom, this.leftRoom);
}
// make sure to change the leftRoom function to use the arrow function syntax:
private leftRoom = () => { ... }
See this docs page for details (note that this is not Needle specific but how javascript events/functions fundamentally work)