Posts: 60
Threads: 12
Joined: Jul 2025
Reputation:
0
Hi, I have several questions about creating constraints during runtime.
As far as I know constraints are stored in blueprints or directly in actor (pin).
Then those constraints are pushed to solver and copy is created and merged in solver.
There is mechanism to make constraints dirty, this causes constraints tobe rebuilt, by taking all actors constraints, however what about stitch constraints?
I can't see them stored in actors or blueprints, so is there only runtime representation? What if I marked stitch constraints as dirty, would that mean that all stitch constraints are cleared and cannot be rebuilt, because there is no blueprint/actor (original) copy?
Also what if I wanted to create some pin constraints during runtime via custom script and modify offsets every step? I want to avoid making constraints dirty every frame, but at the same time it does make sense to modify actor copy in case it's rebuilt, so should I modify both? How can I diffrentiate my own batch from all other solver batches to avoid dirtying?
Posts: 6,704
Threads: 28
Joined: Jun 2017
Reputation:
435
Obi Owner:
09-01-2026, 09:54 AM
(This post was last modified: 09-01-2026, 10:02 AM by josemendez.)
(23-12-2025, 10:40 AM)Qriva0 Wrote: Hi, I have several questions about creating constraints during runtime.
As far as I know constraints are stored in blueprints or directly in actor (pin).
Then those constraints are pushed to solver and copy is created and merged in solver.
There is mechanism to make constraints dirty, this causes constraints tobe rebuilt, by taking all actors constraints, however what about stitch constraints?
I can't see them stored in actors or blueprints, so is there only runtime representation?
Correct, stitch constraints only exist at runtime. They're not part of an actor (since they may affect different actors) and they're not part of blueprints since they're created at runtime.
(23-12-2025, 10:40 AM)Qriva0 Wrote: What if I marked stitch constraints as dirty, would that mean that all stitch constraints are cleared and cannot be rebuilt, because there is no blueprint/actor (original) copy?
In case of stitches there's no "original" (actor/blueprint) copy. We deal with the solver batch directly.
(23-12-2025, 10:40 AM)Qriva0 Wrote: Also what if I wanted to create some pin constraints during runtime via custom script and modify offsets every step? I want to avoid making constraints dirty every frame, but at the same time it does make sense to modify actor copy in case it's rebuilt, so should I modify both?
Yes, you should modify both copies. This is exactly what pinhole constraints do (see ObiPinhole.cs)
(23-12-2025, 10:40 AM)Qriva0 Wrote: How can I diffrentiate my own batch from all other solver batches to avoid dirtying?
You use the actor.solverBatchOffsets dictionary: for each constraint type, it contains a list of start offsets in the solver. Again, ObiPinhole.cs is a good reference for this. You'll notice that every time we modify constraint data (see its UpdateParameters() method), we modify both the actor copy and the solver copy, using the solverBatchOffsets array to know where the solver copy "lives" and differentiate it from all other batches in the solver.
----
As a side note, in Obi 8 we've simplified this system significantly. Batching and clustering are done incrementally at runtime every time a constraint is added/removed (with O(1) cost), so no constraint "dirtying" needed, there isn't any need to expose batches directly to the user, and at runtime you often only deal with one copy of the constraints. It's also more performant since the cost of adding or removing actors no longer depends on how many actors are already in the solver, only on the amount of constraints in the actor.
Basically just int index = AddConstraint(), and then you can use the index to access constraint data in the solver arrays. When dealing with blueprints, there's an array that maps index in the blueprint to index in the solver.
Posts: 60
Threads: 12
Joined: Jul 2025
Reputation:
0
Quote:In case of stitches there's no "original" (actor/blueprint) copy. We deal with the solver batch directly.
I think this is cause of my problems described in the other thread. Stitcher can be disabled during simulation, and the same happens on destroy and on application quit (called also when exiting playmode). Solver removes actors, but it does not take into account that simulation is still running and doing this is not allowed:
ObiStitcher
Code: lambdas.Dispose();
particleIndices.Dispose();
stiffnesses.Dispose();
ObiSolver
Code: private void OnDestroy()
{
// Remove all actors from the solver. This will trigger Teardown() when the last actor is removed.
while (actors.Count > 0)
RemoveActor(actors[actors.Count - 1]);
}
The fact I need to manage data for simulation this way isnot just clunky, but does not work, so I hope there will be proper improvement in Obi 8.
Is there reasonable way to manage this with older obi versions?
Posts: 6,704
Threads: 28
Joined: Jun 2017
Reputation:
435
Obi Owner:
02-02-2026, 01:34 PM
(This post was last modified: 02-02-2026, 05:04 PM by josemendez.)
(02-02-2026, 11:56 AM)Qriva0 Wrote: I think this is cause of my problems described in the other thread. Stitcher can be disabled during simulation, and the same happens on destroy and on application quit (called also when exiting playmode). Solver removes actors, but it does not take into account that simulation is still running and doing this is not allowed:
Unity may call OnDestroy, OnEnable or OnDisable() at any time. Removing an actor doesn't really touch any memory that threads read/write from during simulation, so removing an actor has no ill effect: it just pushes the indices of particles belonging to the actor to a free list so that they can be reused by actors added afterwards (adding an actor to the solver is a buffered operation, guaranteed to not happen during simulation). Constraints are simply set dirty and updated at the start of the next simulation step. This is an expensive operation, but unfortunately it's the way it works in Obi 7.
Upon removing the last actor, the solver calls its Teardown() method which does free up resources that may be in use by other threads, but it waits for the current simulation step (if any) to complete before destroying any resources that may be used by other threads.
(02-02-2026, 11:56 AM)Qriva0 Wrote: The fact I need to manage data for simulation this way is not just clunky, but does not work
In Obi 7 constraints stuff wasn't really designed to be extendable by users, so I totally understand it feels clunky and brittle: unfortunately it is.
When dealing with multithreaded code, you can't modify memory accessed by multiple threads while the threads are running or you risk race conditions. There's basically 3 ways to allow users to interact with an API that uses multithreading under the hood:
- Have a boolean "are multiple threads currently running" and forbid calls to specific methods when that boolean is true, forcing the main thread to poll this boolean until it's allowed to do whatever it is trying to do.
- Force users to do things in specific callbacks called by the engine at a specific point in time that's guaranteed to have no threading issues. This is a slightly cleaner version of method #1.
- Double-buffer all memory that may be modified from the main thread while the simulation is working on a copy of it. As far as API cleanliness goes this is the best method as for the most part it makes multithreading transparent to the user, but it can be memory hungry.
(02-02-2026, 11:56 AM)Qriva0 Wrote: so I hope there will be proper improvement in Obi 8.
In Obi 7 there's no explicit safely rails in place. Executing constraint code at the wrong time can (and will) blow things up.
Obi 8 uses all of the above methods, mainly the third one. This means you can add/remove/modify constraints while simulation is running, which simplifies things and makes writing code that extends the engine a great deal more robust. We're making sure all constraint API calls are safe to use at any time, those that cannot have immediate effect are either double buffered or their execution is deferred until there's only one active execution thread.
(02-02-2026, 11:56 AM)Qriva0 Wrote: Is there reasonable way to manage this with older obi versions?
What is it exactly that you're to trying to do? I assume it's some kind of pin constraint/stitch constraint creation at runtime? could you give a few more details so I can recommend the best approach to it in Obi 7?
kind regards
Posts: 60
Threads: 12
Joined: Jul 2025
Reputation:
0
(02-02-2026, 01:34 PM)josemendez Wrote: What is it exactly that you're to trying to do? I assume it's some kind of pin constraint/stitch constraint creation at runtime? could you give a few more details so I can recommend the best approach to it in Obi 7?
Overal I tried to create custom component to manage some constraints, pin or maybe stitch in different way. For example component that does exactly the same thing as pin/attachment constraint, but I can define and modify pinned position and orientation for each particle.
However:
(02-02-2026, 01:34 PM)josemendez Wrote: Unity may call OnDestroy, OnEnable or OnDisable() at any time. Removing an actor doesn't really touch any memory that threads read/write from during simulation, so removing an actor has no ill effect: it just pushes the indices of particles belonging to the actor to a free list so that they can be reused by actors added afterwards (adding an actor to the solver is a buffered operation, guaranteed to not happen during simulation).
This is true for actors, but I am not sure it is for stitch constraint. I didn't do deep dive into whole pathway of calls from solver, but unloading actor triggers dispose of arrays mentioned before, but you said that stitch constraint has no actor copy, and those arrays are pushed directly into batch, so for example when component is disabled during simulation it throws exception, because data used directly in simulation is disposed. And this is not coming from my component, but the original stitcher itself, you can see stack trace here.
I try to write my code inspired on existing Obi components, but this component simply does not work properly and I am not sure what to do.
Thank you for the reply, have a great day!
|