Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  How to Save / Load Obi Actors in 5.0
#1
Hi!

I'm trying to load and save the state of an obi actor, but so far I did not manage to: I am used to the old way involving irectly getting the data from the actor after pulling it from the solver.

How should I approach this now? 
I tried to create a blueprint, save the actor to it, and then load the blueprint, but so far no luck.

Is that the way I should do it?

Here is my current approach:

Code:
public object Save()
   {
       //   var blueprint = ScriptableObject.CreateInstance<ObiActorBlueprint>();
       ObiActorBlueprint bp = (ObiActorBlueprint) ScriptableObject.CreateInstance(Actor.blueprint.GetType());
       bp.GenerateImmediate();
       Actor.SaveStateToBlueprint(bp);
       
       //am I saving that properly?
       return bp;
   }

   public void Load(object saveFile)
   {
       if (saveFile == null)
           return;

       //needed? no idea
       var solver = Actor.solver;
       Actor.RemoveFromSolver();

       var blueprint = (ObiActorBlueprint) saveFile;
       Actor.blueprint = blueprint;

       //doing that doesnt work
       ///blueprint.GenerateImmediate();

       Actor.transform.SetParent(solver.transform);
       Actor.AddToSolver();

       //no working
       Actor.LoadBlueprint(solver);
   }
Reply
#2
(25-11-2019, 06:51 PM)Bill Sansky Wrote: Hi!

I'm trying to load and save the state of an obi actor, but so far I did not manage to: I am used to the old way involving irectly getting the data from the actor after pulling it from the solver.

How should I approach this now? 
I tried to create a blueprint, save the actor to it, and then load the blueprint, but so far no luck.

Is that the way I should do it?

Here is my current approach:

Code:
public object Save()
   {
       //   var blueprint = ScriptableObject.CreateInstance<ObiActorBlueprint>();
       ObiActorBlueprint bp = (ObiActorBlueprint) ScriptableObject.CreateInstance(Actor.blueprint.GetType());
       bp.GenerateImmediate();
       Actor.SaveStateToBlueprint(bp);
       
       //am I saving that properly?
       return bp;
   }

   public void Load(object saveFile)
   {
       if (saveFile == null)
           return;

       //needed? no idea
       var solver = Actor.solver;
       Actor.RemoveFromSolver();

       var blueprint = (ObiActorBlueprint) saveFile;
       Actor.blueprint = blueprint;

       //doing that doesnt work
       ///blueprint.GenerateImmediate();

       Actor.transform.SetParent(solver.transform);
       Actor.AddToSolver();

       //no working
       Actor.LoadBlueprint(solver);
   }

Hi Bill,

Storing state to a blueprint only stores particle positions and velocities, so the blueprint you use as storage should be instantiated from the original rope's blueprint.
Will add a sample of this use case to the documentation today.
Reply
#3
Thanks a lot!
Reply
#4
Some more info, if I do this:
Quote:
Code:
   public object Save()
   {
       ObiActorBlueprint bp = Instantiate(Actor.blueprint);
       Actor.SaveStateToBlueprint(bp);
       return bp;
   }

   public void Load(object saveFile)
   {
       if (saveFile == null)
           return;
       
       var solver = Actor.solver;
       Actor.RemoveFromSolver();

       var blueprint = (ObiRopeBlueprint) saveFile;
     
       Actor.ropeBlueprint = blueprint;
       Actor.LoadBlueprint(solver);
   }
then there is an exception on load:
(The odin inspector part is just the button press in the editor)
Code:
NullReferenceException: Object reference not set to an instance of an object
Obi.ObiActor.LoadBlueprintParticles (Obi.ObiActorBlueprint bp) (at Assets/Plugins/Obi/Scripts/Common/Actors/ObiActor.cs:764)
Obi.ObiActor.LoadBlueprint (Obi.ObiSolver solver) (at Assets/Plugins/Obi/Scripts/Common/Actors/ObiActor.cs:915)
Obi.ObiRope.LoadBlueprint (Obi.ObiSolver solver) (at Assets/Plugins/Obi/Scripts/RopeAndRod/Actors/ObiRope.cs:105)
ObiRopeSaver.Load (System.Object saveFile) (at Assets/Plugins/BFT/BFT/Plugins Extensions/Obi/Save/ObiRopeSaver.cs:28)
BFT.SavedObjectListComponent.Load (System.Object saveFile) (at Assets/Plugins/BFT/BFT/Serialization/Game Save/Lists/SavedObjectListComponent.cs:55)
BFT.SavedObjectListComponent.Load (System.Object saveFile) (at Assets/Plugins/BFT/BFT/Serialization/Game Save/Lists/SavedObjectListComponent.cs:55)
BFT.BFTAction`1[T].Invoke (T val) (at Assets/Plugins/BFT/BFT/Referencing/Generic/Delegates/Actions/BFTAction1.cs:226)
BFT.FunctionToActionSetter`3[T,T1,T2].SetValue () (at Assets/Plugins/BFT/BFT/Referencing/Generic/Delegates/Function/FunctionToActionSetter.cs:24)
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <437ba245d8404784b9fbab9b439ac908>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <437ba245d8404784b9fbab9b439ac908>:0)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer.InvokeButton () (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/Misc Drawers/DefaultMethodDrawer.cs:329)
UnityEngine.Debug:LogException(Exception)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:InvokeButton() (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/Misc Drawers/DefaultMethodDrawer.cs:344)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:DrawNormalButton() (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/Misc Drawers/DefaultMethodDrawer.cs:217)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:DrawPropertyLayout(GUIContent) (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/Misc Drawers/DefaultMethodDrawer.cs:129)
Sirenix.OdinInspector.Editor.OdinDrawer:DrawProperty(GUIContent) (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinDrawer.cs:176)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw(GUIContent) (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Core/InspectorProperty.cs:544)
Sirenix.OdinInspector.Editor.InspectorUtilities:DrawPropertiesInTree(PropertyTree) (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Misc/InspectorUtilities.cs:374)
Sirenix.OdinInspector.Editor.PropertyTree:Draw(Boolean) (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Core/PropertyTree.cs:303)
Sirenix.OdinInspector.Editor.OdinEditor:DrawTree() (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinEditor.cs:93)
Sirenix.OdinInspector.Editor.OdinEditor:DrawOdinInspector() (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinEditor.cs:215)
Sirenix.OdinInspector.Editor.OdinEditor:OnInspectorGUI() (at X:/Repositories/sirenix-development/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinEditor.cs:85)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

Additionally, saving and loading during runtime causes a crash of the editor, I attached the dump file but it doesn't seem to bring anything
https://drive.google.com/open?id=1FThylJ...LtNLkFeoup

I also got to realize that I need to save the raw data, not the blueprint since I can't serialize a runtime-created blueprint: how can I modify all the arrays of data manually?
Reply
#5
Hi Bill,

What you're doing in Load is:
- Removing the actor from the solver (which unloads the current blueprint automatically)
- Assigning a new  blueprint (this unloads the current blueprint if it is loaded in the solver, and loads the new one if a solver is present).
- Calling LoadBlueprint() again a second time manually, without re-adding the actor to the solver first. This is very dangerous, as it will write data outside the solver's data arrays -which are raw C pointers- and will likely cause a crash. (LoadBlueprint is public only because it needs to be callable by ObiSolver, but it's not supposed to be called manually. Now I realize I should probably guard against manual calls somehow.)

As you can see there's a lot of redundant stuff happening there, and some things that should happen (like having the rope added to its solver at the end) aren't happening.

Getting this to work is way simpler than it looks. No need to remove the actor from the solver manually, load blueprints yourself or anything. Simply set ropeBlueprint to its new value when you need to use a different blueprint, that's it.

The following example does this, with a caveat: since I'm reusing the same blueprint to store the state over and over again, and I want to force a reload of the blueprint everytime (despite being the same blueprint, it might contain different data), I set it to null first.

Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;

public class StateStoreLoad : MonoBehaviour
{
public ObiRope actor;
   private ObiActorBlueprint state;

   public void Update()
{
if (Input.GetKeyDown(KeyCode.I))
Store();
else if (Input.GetKeyDown(KeyCode.O))
Load();
}

public void Store()
{
       // Only create the state blueprint if we haven't created one yet.
       if (state == null)
           state = Instantiate(actor.blueprint);

       // Store particle positions/velocities to the state blueprint.
actor.SaveStateToBlueprint(state);
}

public void Load()
{
       // If we have no state to load, bail out.
if (state == null)
return;

       // we need this to force a blueprint reload, because we are reusing the same one. Setting the exact same blueprint the actor currently uses will have no effect.
       // we could also destroy "state" after loading it, and re-create it in Store(). Then it would not be necessary to set the rope's blueprint to null before reassigning it.
       actor.ropeBlueprint = null;
actor.ropeBlueprint = (ObiRopeBlueprint)state;
}
}

Hope this makes sense! Do not hesitate to reach out if you need further details.
Reply
#6
Saw this out of the corner of my eye before leaving the thread:

Quote:I also got to realize that I need to save the raw data, not the blueprint since I can't serialize a runtime-created blueprint: how can I modify all the arrays of data manually?

You can get/set per-particle data manually quite easily:
http://obi.virtualmethodstudio.com/tutor...icles.html

The solver contains all currently loaded particle data in its arrays. solver.positions and solver.velocities are probably the ones you're most interested in.

When an actor is loaded in a solver, the solver allocates space for the particles in a greedy way: for each actor particle, it finds the first empty slot in the data arrays, and places it there.  The actor knows where each particle is placed in the solver because the solver fills in the actor's solverIndices array with the index for each particle.

So, to modify the velocity of the first particle in the actor:
Code:
solver.velocities[actor.solverIndices[0]] = new Vector4(1,0,0,0);

Same to retrieve it. Note: The fourth member (x,y,z,w) in positions/velocities is simply there for nicer memory alignment, should be 0 always.
Reply
#7
Got it, thanks a lot for the detailed explanation. I think I won't be using blueprints but directly get/set the data from the solver.
Reply
#8
Hi Jose!
Thank you for great solution for loading/saving the blueprints! I've digged half of the forum and finally found it hereSonrisa
It is almost what I need except one point: seems that it saves only in memory and keep them while game is running. But how can I save it permanently?
I want to edit my ropes in runtime (I'd like to make some kind of knots and it is quite difficult to achieve using the editor) and then save them to disk. I tried to save ropes as prefabs but this keeps bluprints unmodified. Do you have any solution for this case?
Reply
#9
(25-10-2020, 09:51 PM)Elegar Wrote: Hi Jose!
Thank you for great solution for loading/saving the blueprints! I've digged half of the forum and finally found it hereSonrisa
It is almost what I need except one point: seems that it saves only in memory and keep them while game is running. But how can I save it permanently?
I want to edit my ropes in runtime (I'd like to make some kind of knots and it is quite difficult to achieve using the editor) and then save them to disk. I tried to save ropes as prefabs but this keeps bluprints unmodified. Do you have any solution for this case?

Hi there!

In Unity you can't create/serialize new blueprints at runtime, you can only create new blueprints while in editor.

So you do this the same way as you'd serialize any data to disk, such as game progress or user info: pick the binary/text format of your choice (json, xml, or roll your own if the need strikes), then use any API you like to write this file to disk. There isn't any specific way to do this in Obi, or Unity, as there isn't in any engine afaik. You're free to decide how to make your data persistent.

Unity has built-in json support, that will automatically serialize/deserialize objects for you. This simplifies things considerably but still, you need to do this by yourself:
https://docs.unity3d.com/ScriptReference...ility.html

https://www.raywenderlich.com/418-how-to...e-in-unity
Reply
#10
Hi Jose!
What you are talking about is quite different thing.. I guess I didn’t explain well what I want.

I need this for level design. I don't need to save blueprints on user's end like savegame data, I want them to be included in build. Actually creating blueprints from Editor would work for me: I can run the game, make a knot in runtime, pause the game and then move back to scene mode.
For example, in Unity you can save prefabs right from runtime (if you run in Editor). I want to make the same trick with blueprints, is it possible?
Reply