-
-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow avoidance of exclusive World's ownership from Testbed #133
Conversation
I don't think specs requires shared ownership, much less explicit locking, of world state. |
For clarification, the World I am putting in Mutex is the one from nphysics, not the one from specs. The errors I had concerning I'll update the description to make it more explicit. That being said, you might be right, I didn't find any mentions of For now I focus on making my use case work, then I'll try to optimize the data scheme used. Thanks for your feedback. |
You probably encountered the requirements on |
That must be it ! Thanks 🙏 Dropping dispatcher for systems requiring Applying I’ll make examples from all these informations to help visualizing impacts. (When I have time) |
After more testing, this Pull Request kinda works, except for graphics updates. I added this snipper to handle new additions to for co in physics_world.colliders() {
if self.graphics.body_nodes_mut(physics_world, co.data().body()).is_none() {
self.graphics
.add(window, co.handle(), &physics_world);
}
} I think that will not handle modifications to existing 2d bodies, but that's a start, I suggest I focus my efforts on supporting new bodies additions during runtime before going in edge cases like modification, deletion, etc. This Pull request is working at the moment for my (very limited use case).
This checklist is at top level description for readability purposes. |
nphysics_testbed2d/src/testbed.rs
Outdated
.add(window, co.handle(), &physics_world); | ||
} | ||
} | ||
self.graphics.draw(&physics_world, window); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved that outside of the running check (if self.running != RunMode::Stop {
), because I thought this belonged more in the "draw" logic than the update. It allows me to easily share the same mutex for graphics update and graphics rendering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was inside of the running check because the self.graphics.draw(...)
is actually not very well named. All it does is to synchronize the positions of the graphics objects with the position of the physics colliders. Performing this update is not necessary if the simulation is not running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for attempting this! The approach seems sound overall, besides a few details that could be improved to avoid the user having to know about the Arc<Mutex<...>>
in most examples.
One thing that will have to be tested (but I can do this test if you want) is that it still works on WASM.
Don't feel forced to do the same for the 3D testbed, especially since you don't use it.
examples2d/balls2.rs
Outdated
@@ -80,7 +82,7 @@ fn main() { | |||
/* | |||
* Set up the testbed. | |||
*/ | |||
let mut testbed = Testbed::new(world); | |||
let mut testbed = Testbed::new(Arc::new(Mutex::new(world))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be avoided for several reasons:
- It means all the testbed examples will have to be modified.
- This complicates the example with something that is useful only in some corner-use-cases.
- I don't want users looking at the examples to think using an
Arc<Mutex<...>>>
is always necessary. (Someone quickly skimming through the examples just to see what they look like will notice theuse
ofArc
andMutex
so they may end up concluding too quickly).
An alternative is to keep the Testbed::new(world)
as it is, and have a method from the testbed to retrieve the shared world: testbed.world()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My alternative take on this was to make different instantiaters to pass world to Testbed, from my WIP local branch:
/// This trait is designed to allow choosing implementation of underlying storing of World: shared between threads or owned only by Testbed.
pub trait WorldOwner {
fn get<'a: 'b, 'b>(&'a mut self) -> Box<DerefMut<Target = World<f32>> + 'b>;
}
pub struct WorldOwnerShared {
world: Arc<Mutex<World<f32>>>,
}
impl WorldOwnerShared {
pub fn new(w: World<f32>) -> WorldOwnerShared {
WorldOwnerShared {world: Arc::new(Mutex::new(w))}
}
}
impl WorldOwner for WorldOwnerShared {
fn get<'a: 'b, 'b>(&'a mut self) -> Box<DerefMut<Target = World<f32>> + 'b> {
Box::new(self.world.lock().unwrap())
}
}
// TODO: simpler WorldOwnerExclusive implementation not requiring mutex
that would impact my use case example as
let mut testbed = Testbed::new(Testbed::WorldOwnerShared(world)));
And existing simpler examples as
let mut testbed = Testbed::new(Testbed::WorldOwnerExclusive(world)));
Maybe the world itself could implement WorldOwner, so the existing examples wouldn't be impacted at all ? 💃
Edit: a From<nphysics::World>
to implement for both WorldOwner
s might seem an even better idea, I'll play with the idea
Your suggestion makes it difficult or overkill to share TestBed
with specs::System
, in order to retrieve nphysics::World
. Please tell me if my lead is incorrect so I don't spend time in a incorrect direction.
All this discussion is underlying to the goal of objective
allow to avoid heavy Arc<Mutexnphysics::World> if not required by (library) user."
from the TODO List
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your suggestion makes it difficult or overkill to share TestBed with specs::System, in order to retrieve nphysics::World.
There is no need to share the testbed with specs. What I was thinking of would mean your example would look like that:
// Initialize the testbed first.
let mut testbed = Testbed::new(physics_world);
let ecs_world = std::cell::RefCell::new(specs::World::new());
// In the following, we have testbed.world() -> Arc<Mutex<World<f32>>
ecs_world
.borrow_mut()
.add_resource(testbed.world());
let dispatcher = DispatcherBuilder::new()
.with(DummySystem::new(), "dummy_system", &[])
.build();
let dispatcher = std::cell::RefCell::new(dispatcher);
);
Basically, the testbed is responsible for creating the Arc
internally.
If think your solution based on WorldOwner
is better than what I proposed above. The testbed could be parameterized by: Testbed<W: WorldOwner>
, and instead of returning Box<DerefMut<Target = World<f32>> + 'b>>
, the .get
method you propose could return impl DerefMut<Target = World<f32>> + 'b>
to avoid boxing.
As you proposed, both World
, Arc<RwLock<World>>
and Arc<Mutex<World>>
should implement this WorldOwner
trait so no modification of any testbed example is required (even when user callbacks are used since they would become Fn(&mut T, &mut GraphicsManager, f32)
.
I don't think you will be able to make a solution based on From<nphysics::World>
without requiring the user to add explicit type annotation for the testbed though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I thought about bypasses modifications of existing examples 🎉 :
pub fn set_world(&mut self, world: World<f32>) {
self.set_world_owner(Box::new(WorldOwnerExclusive::from(world)));
}
/// Lower level setter allowing for shared World behaviour
pub fn set_world_owner(&mut self, world: Box<WorldOwner>) ...
I tried experiencing with Into
and From
for World and Arc<RwLock>
, but didn't succeed in making the situation better, I'll stop there my experiments on set_world
API modifications and consider it OK enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did try to make get
return a impl WorldOwned
, but I failed at making it compile again, so I'll let this as an exercise to whoever is more confortable with that.
nphysics_testbed2d/src/testbed.rs
Outdated
.add(window, co.handle(), &physics_world); | ||
} | ||
} | ||
self.graphics.draw(&physics_world, window); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was inside of the running check because the self.graphics.draw(...)
is actually not very well named. All it does is to synchronize the positions of the graphics objects with the position of the physics colliders. Performing this update is not necessary if the simulation is not running.
nphysics_testbed2d/src/testbed.rs
Outdated
window: Option<Box<Window>>, | ||
graphics: GraphicsManager, | ||
nsteps: usize, | ||
callbacks: Vec<Box<Fn(&mut World<f32>, &mut GraphicsManager, f32)>>, | ||
callbacks: Vec<Box<Fn(&mut GraphicsManager, f32)>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not very fond of this, though I understand why this can be necessary to avoid dead-locks if, e.g., the user locks the physics world while inside of the callback. Maybe this could at least take &mut Testbed
as its first argument, so the user can call testbed.world()
to retrieve the shared world (see my first comment for the definition of testbed.world()
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, or pass the self.world
without locking it, to let the user choose between using the parameter or handling himself the dependency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that will work too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference: passing &mut Testbed
in callback caused cannot reborrow mutably
error and that's why I just plain removed it.
passing the self.world
needs Testbed
to be generic over its WorldOwner
real type. I wanted to avoid adding complexity, but as you suggested it in other comments, I'll look into it.
nphysics_testbed2d/src/testbed.rs
Outdated
@@ -50,11 +52,11 @@ fn usage(exe_name: &str) { | |||
} | |||
|
|||
pub struct Testbed { | |||
world: World<f32>, | |||
world: Arc<Mutex<World<f32>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why you used a Mutex
instead of a RwLock
? The latter should be more flexible by allowing multiple simultaneous read-only locks.
To abstract out storage strategy for nphysics::World
131 testbed world owner
3d2b3b2
to
9c642f8
Compare
In order to avoid breaking changes, examples don't need modifications to compile and work!
782bf93
to
1288775
Compare
@sebcrozet You wrote somewhere that webasm should be tested. I'm not very comfortable with webassembly and rust, so if you have resources on how to make it work with nphysics, I may follow some guide. Until then, I think this Pull Request is good for "final" reviews. Unchecked items from the top TODO List will need more guidance for me to check them. That is, if they are strictly needed, that is for you to decide. Concerning CI, I'm not sure why it's failing, I had no problem on mac nor on windows, making cargo build from nightly and 1.28. |
@Vrixyz Thanks, this PR seems pretty good and almost ready now! I appreciate that your changes don't impact the examples, except for those that fail to compile on travis. The reason why some examples fail to compile on travis come from the fact the For testing on WebAssembly, you may follow that tutorial on the user guide: http://nphysics.org/wasm_compatibility/ For testing, you can do this on the I will take some time next week to finalize this PR (by performing similar changes to the 3D testbed) and merge it when CI succeeds. If you want, I can do the WebAssembly tests next week as well. |
Thanks, I'll attempt to fix 2d examples, it sound simple to fix. Once done, I'll make another go at the webassembly testing. After that I'll stop touching this PR, I'll keep you updated, that should be soon enough. |
There's definitely something I'm missing for understanding and testing webasm : when I try to load I'm stopping here, CI is green 🎉 |
261e669
to
20d475c
Compare
I've tested with wasm32-unknown-unknown and it works as expected. I can finally merge this. Thanks @Vrixyz! |
Referencing #131
Current state
I succeeded in compiling:
I'm opening the pull request to show my progress, and discuss about the shortcomings.
TODO List
Arc<Mutex<nphysics::World>>
if not required by (library) user.From<nphysics::World>
for no impact on current examplesRwLock
instead ofMutex
sensor2
andbodystatus2
use callback whose signature changed.Tasks this PR will certainly not cover
They should be copied over to #131 to keep track of them.
impl
: as commented, I couldn't make it work.alternate simpler example withoutI did not succeed in applying this ideaDispatcher
and its need ofMutex
.Failed attempts
This section is only for reference, to help understand why some choices have been made.
Very first attempt
Within Testbed, I tried to replace
World
by&mut World
, no chance in compiling ever after.Second attempt
Within Testbed, I tried to add a "WorldOwner", which objective was to own a reference to a &mut World, then have a
retrieve
function to access it.I lost myself again in complex lifetime annotations.
Third attempt
I started small, read some articles (again)
Then, I used
RefCell<World>
to store a mutable world insideTestbed
.Once compiling and at least 1 official example working, I used
Rc<RefCell<World>
.Balls2d compiled successfully, but not my reproduction repository : indeed, specs::System needs its resources to be
sync::Send
to be sent beetween threads.So I tried to
Arc<RefCell<World>>
, but I quickly realized I had no choice but to useArc<RefCell<World>>
, sinceRefCell
is notsync::Send
.Minor mistakes
I thought I had a deadlock, but it was actually lack of gravity 🙄; then
graphicsManager
was not called when modifyingnphysics::World
, so it leads to a crash when attempting to modify color when moving an object. I fixed it by updating graphics nodes after the update loop.