Skip to content
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

[Lua] SimulatedBody.GetOnCollisionBeginEvent() issue #17630

Open
yaakuro opened this issue Mar 18, 2024 · 7 comments
Open

[Lua] SimulatedBody.GetOnCollisionBeginEvent() issue #17630

yaakuro opened this issue Mar 18, 2024 · 7 comments
Labels
kind/bug Categorizes issue or PR as related to a bug. priority/major Major priority. Work that should be handled after all blocking and critical work is done. sig/simulation Categorizes an issue or PR as relevant to SIG Simulation

Comments

@yaakuro
Copy link
Contributor

yaakuro commented Mar 18, 2024

Describe the bug
When using Lua to detect collisions through the SimulatedBody.GetOnCollisionBeginEvent(...) function, it consistently returns nil. The reason for this issue can be found in the RigidBodyComponent.cpp file, where the following line exists:

    void RigidBodyComponent::Activate()
    {
      ....
        // During activation all the collider components will create their physics shapes.
        // Delaying the creation of the rigid body to OnEntityActivated so all the shapes are ready.
        AZ::EntityBus::Handler::BusConnect(GetEntityId());
    }

    void RigidBodyComponent::OnEntityActivated([[maybe_unused]] const AZ::EntityId& entityId)
    {
        AZ::EntityBus::Handler::BusDisconnect();

        // Create and setup rigid body & associated bus handlers
        CreateRigidBody();

        // Add to world
        EnablePhysics();
    }

Upon examination, it appears that the creation of the rigid body is intentionally being deferred, as noted in the accompanying comment. However, this delay leads to a problem when attempting to detect collisions via Lua within the OnActivate() function.

function CollisionHandling:OnActivate()
    self.event = SimulatedBody.GetOnCollisionBeginEvent(self.entityId)
    self.CollisionBeginEvent = self.event:Connect(
        function(_, collision)
            Debug.Log("------>")
        end
    );
end

Not sure if this is my wrong doing or this is really a problem.

Steps to reproduce
Steps to reproduce the behavior:

  1. Create Level
  2. Create 2 entities with rigid body component
  3. Add lua component and try to use the GetOnCollisionBeginEvent()
  4. Start Game
  5. See error

Expected behavior
SimulatedBody.GetOnCollisionBeginEvent(self.entityId) should not return nil.

Actual behavior
A clear and concise description of what actually happened.

Screenshots/Video
If applicable, add screenshots and/or a video to help explain your problem.

Found in Branch
SimulatedBody.GetOnCollisionBeginEvent(self.entityId) returns nil.

Commit ID from o3de/o3de Repository
26baf4a

Desktop/Device (please complete the following information):

  • Device: PC
  • OS: Linux
  • Version Kubuntu 22.04.3
  • CPU Ryzen 5950X
  • GPU RX 6950 XT
  • Memory 128GB
@yaakuro yaakuro added kind/bug Categorizes issue or PR as related to a bug. needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Mar 18, 2024
@nick-l-o3de
Copy link
Contributor

nick-l-o3de commented Mar 19, 2024

IMO, Things do need their collision to work on frame 1, not frame 2. It sounds like its missing some sort of architecture here to call back when the shapes are ready, or added, or something, so that by the time activate is done, it can immediately collide. Alternatively, a layer of abstraction so that the event is always available to connect to, even if the shapes take a frame. Maybe a call at the bottom of OnEntityActivated for Physics Is Ready or something

@yaakuro
Copy link
Contributor Author

yaakuro commented Mar 19, 2024

I checked the BaseColliderComponent and to me it doesn't look like that the InitShapes needs to be done in the Activate method. It can be put into Init so the shapes will be ready when the components gets activated and the rigid bodies are created. It looks to me redundant to Init the shapes and clear everytime we activate and deactivate the component.

Also I was thinking, why would we want to create and destroy a rigid body ever time we activate/deactivate the component? Is there a reason it is as it is right now? Why not just create once then modify the instance and reset if needed.

@lemonade-dm lemonade-dm added sig/simulation Categorizes an issue or PR as relevant to SIG Simulation and removed needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Mar 20, 2024
@nick-l-o3de
Copy link
Contributor

nick-l-o3de commented Mar 25, 2024

regarding activate/deactivate / Init...

The opposite of Activate, is Deactivate. After deactivate, it can be reactivated again.
The opposite of Init is delete (destructor). It cannot be salvaged after the destructor.

Its possible to add and remove components (including the rigid body component and collider components themselves) while the entity is deactivated, but not while active. This guarantees that when you activate and successfully cache some pointer to something, like a shape, or a callback, or an event, or etc, then, until Deactivate, all pointers to other components cached for performance remain valid and do not need to be re-fetched every frame.

Its also by convention when components are supposed to be "listening and active in the world", interacting with other stuff...
Initialized, but non-active components exist elsewhere, such as for example, when deserialized from file during compilation, or in the undo stack, or in editor component wrappers... they may be init, but are not active.

Actually adding rigid bodies and shapes to the world during that phase (parallel compilation or "dead entities" in the undo stack or component wrappers for game-only entities) would be a super bad idea and would potentially flood the "real" editor world with superflous rigid bodies and shapes that are not supposed to be doing anything yet.

This is probably why shapes are destroyed during deactivate and created during activate. Last thing you want is shapes actually being added to a physx world when its loading entities (deserialize + init, but not activate) in the Asset Processor to crunch them into spawnables. There may be some way to cache them so that they do not disappear entirely in deactivate, but do if their component is deleted though ---?

That being said, it does sound like there's a sort of sequence of events here, to me it sounds like we are missing a callback of some sort here, so that things that depend on physics can hook into when physics is ready, still part of activate...?

Like Activate() -> Add Rigid Bodies/Shapes -> PhysicsReady -> OnPhysicsReady -> do stuff? Its tempting to add a "AfterActivate" callback but that is a treacherous road to travel since usually that results in someone going "but I need to do something After, AFTER, AFTER activate..."

@yaakuro
Copy link
Contributor Author

yaakuro commented Mar 26, 2024

Fair enough. I digged more into this and it looks like, that RigidBodyNotifications is used to handle initialization in using the events

        virtual void OnPhysicsEnabled([[maybe_unused]] const AZ::EntityId& entityId)
        virtual void OnPhysicsDisabled([[maybe_unused]] const AZ::EntityId& entityId)

In short, in Lua it would like this:

function MyLuaScript:OnActivate()
        self.RigidBodyNotificationBusHandler = RigidBodyNotificationBus.Connect(self, self.entityId)
end

function MyLuaScript:OnDeactivate()
        self.RigidBodyNotificationBusHandler:Disconnect()
end

So looking into some examples like in ForceRegionComponent.cpp. It does this:

Activate: Connect to RigidBodyNotificationBus
Deactivate: Disconnect from RigidBodyNotificationBus

This however again, causes an issue on the Lua level because when disconnecting at OnDeactivate from the RigidBodyNotificationBus, the OnPhysicsDisabled event is not fired.

So using the new bus, RigidBodyNotificationBus I thought would help to "fix" this issue but then again, disconnecting on OnDeactivate seem to cause another unexpected issue.

@nick-l-o3de
Copy link
Contributor

nick-l-o3de commented Mar 29, 2024

I'm not sure I 100% follow, I was expecting something like this ,with scopes not overlapping

  Entity Activated ---> lua invokes OnActivate
      Entity finishes setting up physics --> lua invokes OnPhysicsEnabled, you can now do stuff with physics
      .... game ticks here for a while
      ... time pases
   Entity is deactivated
        The physics are destroyed -- OnPhysicsDisabled is invoked
        Lua OnDeactivate is invoked.

I'm guessnig the order of that destruction is switched?
It shouldn't be - deactivation / destrction should be following the reverse order. IMO it would be a bug if not... a bug we can fix for sure...

I can understand though, why someone might want to invoke the lua OnDeactivate at the beginning, before the entity is actually deactivated, so that all the systems will still in place and lua can "do stuff" using the entity - an actual deactivated entity can't be talked to and all its busses are already gone... but I'm not sure that's idomatic when shutdown should be happening in reverse order to startup...

(I took a break here to look at the code).

Interesting, it looks like onActivate is invoked when the ScriptComponent (the LUA Script component that you literally add to the entity in the inspector) is Activate()d, and onDeactivate is invoked when that same component is deactivated during entity deactivation.

I'm pretty sure components are activated (and deactivated) in the order they appear in the component inspector panel, and in REVERSE when deactivating, so you should definitely see
OnActivate, OnPhyicsEnabled, OnPhysicsDisabled, OnDeactivate...

@yaakuro
Copy link
Contributor Author

yaakuro commented Apr 1, 2024

Here this is a simple Lua script. I do assume, connecting is done in the correct way, because I would assume, I do connect to RigidBodyNotificationBus in OnActivate, and disconnect in the OnDeactivate:

local CollisionHandlingTest = 
{
    Properties =
    {
    }  	
}

function CollisionHandlingTest:OnActivate()
    Debug.Log("OnActivate")

    -- Connect to the RigidBodyNotificationBus
    self.RigidBodyNotificationBusHandler = RigidBodyNotificationBus.Connect(self, self.entityId)
end

function CollisionHandlingTest:OnDeactivate()
    Debug.Log("OnDeactivate")

    -- Disconnect from the RigidBodyNotificationBus
    self.RigidBodyNotificationBusHandler:Disconnect()
end

function CollisionHandlingTest:OnPhysicsEnabled(entityId)
    Debug.Log("OnPhysicsEnabled")
end

function CollisionHandlingTest:OnPhysicsDisabled(entityId)
    Debug.Log("OnPhysicsDisabled")
end

return CollisionHandlingTest

This is a picture of the console:

image

As you can see, the OnPhysicsDisabled event is not triggered because we do disconnect in OnDeactivate. If I move the
self.RigidBodyNotificationBusHandler:Disconnect() into the OnPhysicsDisabled function itself, it will work

local CollisionHandlingTest = 
{
    Properties =
    {
    }  	
}

function CollisionHandlingTest:OnActivate()
    Debug.Log("OnActivate")

    -- Connect to the RigidBodyNotificationBus
    self.RigidBodyNotificationBusHandler = RigidBodyNotificationBus.Connect(self, self.entityId)
end

function CollisionHandlingTest:OnDeactivate()
    Debug.Log("OnDeactivate")


end

function CollisionHandlingTest:OnPhysicsEnabled(entityId)
    Debug.Log("OnPhysicsEnabled")
end

function CollisionHandlingTest:OnPhysicsDisabled(entityId)
    Debug.Log("OnPhysicsDisabled")

    -- Disconnect from the RigidBodyNotificationBus
    self.RigidBodyNotificationBusHandler:Disconnect()
end

return CollisionHandlingTest

Here the console:

image

So yes, the order is not good. I am not sure why I am the only one having this issue and If everyone is using Script Canvas, is it handled differently?
In my point of view as a user I do expect buses to be connected in the OnActivate of the lua function, and disconnected in the OnDeactivate. Everything else is misleading and wrong architecture. Even though I tried to use the RigidBodyNOtificationBus, this also lead to wrong usecases. I think the Init, Activate and Deactivate is not enough to handle all usecases in O3DE.
Also not having any documentation on this is not good for O3DE beginners. This is frustrating. I might make some tutorials on this bus, still this issue needs to be handled.
So any ideas how to handle (what do we need to change, fix etc.). Meaning, fix the order? Add more events where O3DE guarantees all busses are ready to be connected, disconencted etc.

@yaakuro
Copy link
Contributor Author

yaakuro commented Apr 1, 2024

Also moving the component up and down in the inspector does not have any effect, because even though I put the lua script at the most top. when saving everything, restarting the editor, and looking at the order, the lua script is again at the bottom.

@michalpelka michalpelka added priority/major Major priority. Work that should be handled after all blocking and critical work is done. and removed needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Apr 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Categorizes issue or PR as related to a bug. priority/major Major priority. Work that should be handled after all blocking and critical work is done. sig/simulation Categorizes an issue or PR as relevant to SIG Simulation
Projects
None yet
Development

No branches or pull requests

4 participants