Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Suggestion / Idea  Problem with the Obi Solver Structure
#1
Hi,

First of all congratulation on releasing the 5.0 version: the edition of ropes definitely got much easier, I like it!

I must, however, come with a major concern when it comes to the project structure: the fact that actors need to be children of an obi solver is a big problem, for a few reasons:
  • It makes it impossible to create rope prefabs and to edit them in prefab mode, which is now core in production for unity 2019+: the ropes do not have a solver in this, so it can't be edited properly. But most of my work, and I suspect most of the users will be in the same scenario, revolves around nested prefabs.
  • It makes it hard to link to a solver when being in a multiscene scenario, which is I think very common in current Unity's workflow
  • It forces a hierarchy logic that is often not desirable, especially so when the solvers are not colliding with each other.
Would it be possible to have an architecture that would link the solver through a scriptable object reference for instance? This way it's compatible with both a proper prefab workflow, a multi-scene project architecture, and makes the hierarchy free?

I have another similar problem concerning the new attachment system: because it must be a component on the same game object as the obi actor, it makes the architecture not very flexible and not easy to use with the classic drag and drop for fields. If I have a custom component that wants to activate or deactivate that attachment in runtime, linking them in the editor becomes an annoyance. 
Would it be possible to make it a component that can be added anywhere with a reference to the obi actor it constrains? this would be much more flexible.

Except for those two points, thanks for the awesome work!
Reply
#2
Hi Bill!

Thanks for the deep feedback! Very few people take the time to think about architectural decisions and build constructive criticism this way, so I really appreciate it.
We weighted pros and cons very carefully when deciding to switch to this architecture, here are the points we considered (some of them are "answers" to yours):

- This design is identical to Unity's UI architecture: their Canvas is our Solver, and UI panels our Actors. It's tried and tested, and for most users, a familiar architectural design.

Quote:It makes it impossible to create rope prefabs and to edit them in prefab mode, which is now core in production for unity 2019+: the ropes do not have a solver in this, so it can't be edited properly. But most of my work, and I suspect most of the users will be in the same scenario, revolves around nested prefabs.

- Unless I'm missing something, you can edit ropes fine in blueprint mode. However you won't get visual feedback on the final rendered mesh, only on the path shape (the path spline and the thickness handles are still rendered when no solver is present).

Anyway, there's ways to have a custom prefab stage root object, so in the near future we could add a ObiSolver to the stage root every time you edit a prefab actor without much effort. Looking into it! Edit: seems an easy way to define your own root object for prefab mode is being worked on: https://forum.unity.com/threads/creating...ts.574795/ Edit2: see post below for a working solution  Guiño


Quote:It makes it hard to link to a solver when being in a multiscene scenario, which is I think very common in current Unity's workflow
- Not any harder than before, I think, since you cannot have objects from scene A referencing objects from scene B anyway. Linking to a common solver in 4.X would require to manually set the "actor.solver" property, then calling a custom API method (actor.AddToSolver()). This is arguably more confusing, verbose and error prone (most people forgot to call AddToSolver()) than simply reparenting a transform. In an ideal world, actors in different scenes should use their own solver. In the case where you need to have actors from different scenes interacting with each other, the workaround isn't more difficult than it was before.



Quote:It forces a hierarchy logic that is often not desirable, especially so when the solvers are not colliding with each other.
- Character clothing and soft bodies (both of which are huge use cases in Obi) almost always require local space simulation to be of any use, so the cloth/softbody must be a child of the solver, even in 4.X. We couldn't really find a use case where having actors grouped under a solver meant big trouble.

Also, being able to easily scale/move/rotate the entire simulation using a transform (as if it was rigid object) was an extremely common request among users, and a major source of complaints in support. While it was possible in 4.X by using local-space simulation, setup was not very intuitive, it was *very* easy to get wrong (by enabling local mode but forgetting to parent the actor to the solver), and supporting both local and world space modes required a lot of internal back-and-forth transforming that wasn't cheap in terms of performance.

Allowing an actor to work only when parented to a solver results in an intuitive way to transform the entire thing (that is impossible to set up wrong), unifies both code paths leading to more robust and maintainable code, everything is expressed in the same space which simplifies the API, and on top of that improves performance a bit (not much, but enough to be welcome).


Quote:I have another similar problem concerning the new attachment system: because it must be a component on the same game object as the obi actor, it makes the architecture not very flexible and not easy to use with the classic drag and drop for fields. If I have a custom component that wants to activate or deactivate that attachment in runtime, linking them in the editor becomes an annoyance. 
Would it be possible to make it a component that can be added anywhere with a reference to the obi actor it constrains? this would be much more flexible.

If I understood correctly, the use case you describe is:

- You have a custom component A that wants to activate/deactivate the attachment, so it must have a reference to it.
- Since the attachment must be on the actor object, you need component A to declare a reference to an attachment, so that you can drag/drop the attachment to A.

And the proposed solution:

- Attachments can be added to any object, and have a reference to the actor they attach. So you can create a custom component A that expects an attachment in the same object it is added to.
- Then you have to set up the attachment's reference so that it points to the actor it has to attach.

In both cases you have a reference to an object, and the work you do (both in editor and scripting) is basically the same: you have two components, and one of them must have a reference to another object. The only difference lies in how you write "A": as a component that accepts a reference to an attachment, or a component that "requires" an attachment in the same object.

Also consider what happens when the same actor has multiple attachments. With your solution, attachments could be pretty much anywhere in the scene, and having to look for them all over the place doesn't sound nice. Currently they're all in the actor's inspector, and you can easily see all transforms the actor is attached to listed in the same place, which is pretty neat. Also works in the same way as Unity's joints, so again a familiar design.

This is the same design most existing Unity components have: if a component primarily acts upon or modifies an object, it should be added to that object. Allowing it to be added to any object in the scene, and then referencing the actual object it works on it's a bit messy imho. By your logic, it should be possible to add joints to any object, and have them expose two references to the objects it joins. Or allow MeshRenderers to have a reference to the transform used to transform the mesh.

Let me know your thoughts! Sonrisa
Reply
#3
Hey,

Adding a solver to the prefab stage was much easier than I expected. Add this code anywhere in ObiActor.cs, now everytime you edit an actor prefab the prefab stage contains a solver at its root. Will clean this up a bit and include it in 5.0.1 for next week.

Code:
protected void Awake()
       {
#if UNITY_EDITOR

           // Check if this script's GameObject is in a PrefabStage
           var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(gameObject);

           if (prefabStage != null)
           {
               // Check if parent is null to ensure we are handling the root
               if (gameObject.transform.parent != null)
                   return;

               // Add our own environment root and move it to the PrefabStage scene
               var newParent = new GameObject("ObiSolver (Environment)",typeof(ObiSolver), typeof(ObiLateFixedUpdater));
               newParent.GetComponent<ObiLateFixedUpdater>().solvers.Add(newParent.GetComponent<ObiSolver>());
               UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(newParent, gameObject.scene);
               transform.parent = newParent.transform;
           }
#endif
       }
Reply
#4
Hi! 

Thanks for the detailed answer and the code snippet!

About the architecture, I understand your arguments, but I may have some things to add:

Quote:- This design is identical to Unity's UI architecture: their Canvas is our Solver, and UI panels our Actors. It's tried and tested, and for most users, a familiar architectural design.


While I agree that it is familiar, the UI architecture is quite problematic the new prefab architecture, and I'm not sure the two are really comparable: to me, actors like obi actors are meant to be flexible in the hierarchy so the user can do whatever fits his usage. The UI architecture itself is quite criticized in Unity's forum, so I'm not sure it's necessarily the best example.
In my personal case, it is quite problematic.  Now I understand I can just call AddToSolver and ignore the architecture, but couldn't I just have an ObiSolver field so I could manually set it in the inspector like it used to be, and that would be found by default in the parent on Reset?

Quote:This is arguably more confusing, verbose and error prone (most people forgot to call AddToSolver()) than simply reparenting a transform. 

So does it means that Obi will automatically add the actor to the solver as soon as it's reparented? What if I unparent it, what happens then? That's also confusing to me then: nothing indicates a runtime handling of this here, and if there is it's not necessarily easy to understand (at least to my standard)
Quote:Allowing an actor to work only when parented to a solver results in an intuitive way to transform the entire thing (that is impossible to set up wrong)

I understand, but that also means that you can't configure your obi actor with your own logic and objects together under prefabs: I use obi rope together with cable connectors that can drag and drop, and a whole logic to pool them and save/load them: it makes much more sense project-wise to have the obi actor part of the same prefab, and it also makes it much easier to debug: I suppose I could change the parent of the rope only on runtime, but I find it confusing.

Quote:In both cases you have a reference to an object, and the work you do (both in editor and scripting) is basically the same: you have two components, and one of them must have a reference to another object. 

It is not similar: in your case you are required to:
  1. Open your obi actor inspector, and lock it
  2. Open a second inspector for your object that requires a reference to an attachment
  3. drag and drop the component on the second inspector
  4. Unlock / close the inspector
It's just really not a convenient workflow. Moreover, it doesn't scale well: let's say I would have 20 attachment on one rope, that means 20 components on the same objects: it's both very hard to navigate and also not readable.

Quote:With your solution, attachments could be pretty much anywhere in the scene, and having to look for them all over the place doesn't sound nice


This is really easy to do with the custom search on the scene or using the new Quick Search feature. I would also say that with a proper naming convention, it's really easy to find.
On the contrary, trying to track which component is problematic in a list of 20 or so on the same game object is much harder, because the object pinging won't work, and the reference fields won't indicate which component is used.

Quote:This is the same design most existing Unity components have: if a component primarily acts upon or modifies an object, it should be added to that object. Allowing it to be added to any object in the scene, and then referencing the actual object it works on it's a bit messy imho. By your logic, it should be possible to add joints to any object, and have them expose two references to the objects it joins. Or allow MeshRenderers to have a reference to the transform used to transform the mesh.


For me this is a tad of a semantic consideration here: if my component is on the attachment, then the game object has a component performing a certain action. it acts upon the same object too in the sense that it adds functionality to it.


It is in any way less messy than having multiple components of the same kind of a singular objects: I try to make my game objects respect the single responsibility principle as much as possible, and I find it much easier to operate and navigate, especially that it allows you to use all the navigation features of Unity (pinging, logging, searching...) and to apply any naming convention you may want (contrary to adding many components to the same object since now the object has a lot of responsibilities and a single name, and no ability to ping it or log it properly)

On the top, you could have read-only inspector fields on your ObiActor component that show all the current attachment, so that it is easy to track them from the editor: this way you'd have the best of the two worlds.

One more thing worth mentioning here too: 
Unity 2019.3b with no domain/scene reload option on crashes with a 100% repro when trying to have an obi rope placed not under an obi solver.

I also think that your awake code should look for the root object first because in my case the rope is only one of many game objects in my prefab:

Code:
       protected void Awake()
       {
#if UNITY_EDITOR

           // Check if this script's GameObject is in a PrefabStage
           var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(gameObject);

           if (prefabStage != null)
           {
               //find the root object in the prefab
               Transform transformToReparent=gameObject.transform;

               while (transformToReparent.parent != null)
               {
                   transformToReparent = transform.parent;
               }

               // Add our own environment root and move it to the PrefabStage scene
               var newParent = new GameObject("ObiSolver (Environment)", typeof(ObiSolver),
                   typeof(ObiLateFixedUpdater));
               newParent.GetComponent<ObiLateFixedUpdater>().solvers.Add(newParent.GetComponent<ObiSolver>());
               UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(newParent, gameObject.scene);
               transformToReparent.SetParent(newParent.transform);
           }
#endif
       }
and would that be enough to dynamically switch solver?
Code:
  public ObiSolver solver
       {
           get { return m_Solver; }
           set
           {
               if(m_Solver)
                   RemoveFromSolver();
               m_Solver = value;
               transform.SetParent(m_Solver.transform);
               AddToSolver();
           }
       }
Reply
#5
I really appreciate your insight on this. Very useful conversation to have. Sonrisa

(22-11-2019, 02:18 PM)Bill Sansky Wrote: While I agree that it is familiar, the UI architecture is quite problematic the new prefab architecture, and I'm not sure the two are really comparable: to me, actors like obi actors are meant to be flexible in the hierarchy so the user can do whatever fits his usage. The UI architecture itself is quite criticized in Unity's forum, so I'm not sure it's necessarily the best example.
In my personal case, it is quite problematic.  Now I understand I can just call AddToSolver and ignore the architecture, but couldn't I just have an ObiSolver field so I could manually set it in the inspector like it used to be, and that would be found by default in the parent on Reset?

Personally, I think the UI architecture design is quite good, and fits Obi like a glove: you have a manager object, and a lot of managed objects that need to have a reference frame to perform their stuff in. Having every object reference the manager and then juggling with transforms to express data in the correct space (like we did) is an option. but somehow having them all as a child of the manager seems a lot cleaner to me, as you kill two birds with one stone: all managed objects now have a common clear reference frame, and also an implicit reference to their manager. Its simple, elegant, works well and does not leave margin for half-done setups.

While technically we could let actors reference a solver that could be anywhere, that would make the local space/world space issue to return, as each actor could potentially be simulated in its own space. Not good from usage simplicity, code simplicity, or performance standpoints. Will think of alternatives to this.

(22-11-2019, 02:18 PM)Bill Sansky Wrote: So does it means that Obi will automatically add the actor to the solver as soon as it's reparented? What if I unparent it, what happens then? That's also confusing to me then: nothing indicates a runtime handling of this here, and if there is it's not necessarily easy to understand (at least to my standard)

Yes. If you unparent it, it removes the actor from the solver. If you reparent it to a new solver, then it adds it to the new solver. All this happens internally and you don't need to know about it. Going back to the UI example, you don't need to know that a Canvas keeps a list of all canvas renderers below it and dynamically updates it when reparenting them, but it does. All this is transparent to the user.

An actor is managed by the first solver up its hierarchy, at all times. Simple rule. If no solver can be found, the actor is not simulated.

(22-11-2019, 02:18 PM)Bill Sansky Wrote: I understand, but that also means that you can't configure your obi actor with your own logic and objects together under prefabs: I use obi rope together with cable connectors that can drag and drop, and a whole logic to pool them and save/load them: it makes much more sense project-wise to have the obi actor part of the same prefab, and it also makes it much easier to debug: I suppose I could change the parent of the rope only on runtime, but I find it confusing.

You cannot have a reference to an external object within a prefab (just like you cannot have inter-scene references), so if you want things to work out of the box when instantiating the prefab, you'd need to have both the solver and the rope as part of the prefab. At this point, I don't see the difference between having a solver at your prefab's root, or having all ropes in your prefab reference the solver (that must also be within the prefab).

If the solver lives outside your prefab (in the scene, or an unrelated prefab), then at runtime you will have to either find all ropes in your prefab and set their references to the solver, or simply reparent the whole thing under a solver. If you have multiple ropes per prefab the former is a bit of a chore, the latter is much simpler.

I'm not entirely sure I grasped your use case though, so let me know if this doesn't make sense to you.

Quote:It is not similar: in your case you are required to:
  1. Open your obi actor inspector, and lock it
  2. Open a second inspector for your object that requires a reference to an attachment
  3. drag and drop the component on the second inspector
  4. Unlock / close the inspector
It's just really not a convenient workflow. Moreover, it doesn't scale well: let's say I would have 20 attachment on one rope, that means 20 components on the same objects: it's both very hard to navigate and also not readable.

I was thinking of only 1 attachment in my example, sorry. Yes, in the case you have multiple attachments per actor this is the way to do it. You'd need to lock one inspector to be able to drag the specific attachment you need. However this is true with pretty much everything in Unity: if you have multiple colliders per object, and you want to reference a specific one, the same workflow is required.

Quote:This is really easy to do with the custom search on the scene or using the new Quick Search feature. I would also say that with a proper naming convention, it's really easy to find.
On the contrary, trying to track which component is problematic in a list of 20 or so on the same game object is much harder, because the object pinging won't work, and the reference fields won't indicate which component is used.

It is in any way less messy than having multiple components of the same kind of a singular objects: I try to make my game objects respect the single responsibility principle as much as possible, and I find it much easier to operate and navigate, especially that it allows you to use all the navigation features of Unity (pinging, logging, searching...) and to apply any naming convention you may want (contrary to adding many components to the same object since now the object has a lot of responsibilities and a single name, and no ability to ping it or log it properly)

I think I get the point. If it would make things easier, I can create a second attachment "flavor", that would be added to the target transform and have a reference to the actor (instead of having a reference to the target, and being added to the actor). That way you could have the attachments in the objects the actor is attached to, and have them reference the actor that lives anywhere else. How does this sound?

Quote:On the top, you could have read-only inspector fields on your ObiActor component that show all the current attachment, so that it is easy to track them from the editor: this way you'd have the best of the two worlds.

Not really. This would require the actor to keep track of all attachments referencing it, which makes me feel a bit dirty from an architectural point of view. Ideally the actor should not know anything about attachments. Just like a rigidbody knows nothing about the joints affecting it.
Reply
#6
Quote:Yes. If you unparent it, it removes the actor from the solver. If you reparent it to a new solver, then it adds it to the new solver. All this happens internally and you don't need to know about it.

Got it, that makes sense. So then I think in my example on OnEnable I would need to reparent my rope under my solver object. It does makes things a bit more tricky for my pooling system, but I think I can agree that it's simple and clear.

Quote:If the solver lives outside your prefab (in the scene, or an unrelated prefab), then at runtime you will have to either find all ropes in your prefab and set their references to the solver, or simply reparent the whole thing under a solver. If you have multiple ropes per prefab the former is a bit of a chore, the latter is much simpler.

I'm working on a game that involves a lot of cables with quite some systems of connection on the top, and because of the number of cables in the game I have a respawn pool that handles the pooling of cables in runtime. So it relied on the fact that the ObiRope actor inside my "cable" prefab does not need to be reparented. 

I work with a system of referencing through scriptable object like described in that talk:


This way, I have a reference to my unique Obisolver through a scriptable object reference value, and I was just assigning it on enable. But if I just need to reparent my rope, I could reparent it on enable, and when the cable is pooled just reparent my Obirope under my pooled object. It's less clear but it will do. I did not get the fact that simply reparenting the object would be enough to set the solver.

Quote: If it would make things easier, I can create a second attachment "flavor", that would be added to the target transform and have a reference to the actor (instead of having a reference to the target, and being added to the actor). That way you could have the attachments in the objects the actor is attached to, and have them reference the actor that lives anywhere else.

That would be awesome Sonrisa

Quote:Not really. This would require the actor to keep track of all attachments referencing it, which makes me feel a bit dirty from an architectural point of view. Ideally the actor should not know anything about attachments. Just like a rigidbody knows nothing about the joints affecting it.

I was more thinking of an editor only property, without any serialization involved, but your solution would solve the issue anyway.

As mentioned earlier, there is a crash in 2019.3 that prevents having the actor not parented under a solver (100% repro, at least when using the "disable domain and scene reload" option), but I will investigate that a bit more
Reply
#7
Quote:I also think that your awake code should look for the root object first because in my case the rope is only one of many game objects in my prefab:

Why not having a solver as part of your prefab then?

Back to UI: if you have a cube and a panel parented to it, upon editing the cube prefab no Canvas will be added to the prefab stage root. It is assumed that you'd have a Canvas between the cube and the panel in that case.

If you need to have multiple prefabs containing multiple ropes each, all interacting with each other under, then simply instantiating them under a common solver at runtime solves the issue. No need to manually set references after instantiating them like you'd have to do in previous versions.
Reply
#8
Quote: As mentioned earlier, there is a crash in 2019.3 that prevents having the actor not parented under a solver (100% repro, at least when using the "disable domain and scene reload" option), but I will investigate that a bit more

Didn't have time to battle this beast yet, (disabled domain) was added very recently. Heard it prevents static data from being reinitialized, so chances are we depend on static data somewhere and it just blows up. Will take a look at it soon.
Reply
#9
Quote:Why not having a solver as part of your prefab then?


Let me clarify my use case:

I have only one ObiRope in my prefab, but all my ropes must interact with each other. However my rope is not the root object of my prefab, it has other objects that have a different role, all under one "cable" object that can be pooled.

https://drive.google.com/open?id=1ENJLRx...sxl-LgNOZ4

That is why I need to check for all the parent transform since my ObiRope is not the root object
Reply
#10
Quote:
Quote: If it would make things easier, I can create a second attachment "flavor", that would be added to the target transform and have a reference to the actor (instead of having a reference to the target, and being added to the actor). That way you could have the attachments in the objects the actor is attached to, and have them reference the actor that lives anywhere else.

That would be awesome Sonrisa

We have a deal then Sonrisa. Next week I will have it done.
Reply