From 5f89b69540d6fa6e831eed7360949c67fc4038d2 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sun, 19 Jan 2025 18:05:19 +1300 Subject: [PATCH 1/2] Avoid unnecessary DirtyField() calls --- RELEASE-NOTES.md | 2 +- .../GameObjects/ClientEntityManager.cs | 24 +++- .../SharedContainerSystem.Insert.cs | 4 + .../EntityManager.ComponentDeltas.cs | 25 +++- .../GameObjects/EntityManager.Components.cs | 3 +- .../GameObjects/EntitySystem.Proxy.cs | 7 ++ .../Systems/SharedPhysicsSystem.Components.cs | 114 ++++++++---------- .../Physics/Systems/SharedPhysicsSystem.cs | 7 ++ 8 files changed, 117 insertions(+), 69 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ee0903b530e..795110e630c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Added `EntityManager.DirtyFields()`, which allows components with delta states to simultaneously mark several fields as dirty at the same time. ### Bugfixes diff --git a/Robust.Client/GameObjects/ClientEntityManager.cs b/Robust.Client/GameObjects/ClientEntityManager.cs index 3b33663a8c0..bef870d34d4 100644 --- a/Robust.Client/GameObjects/ClientEntityManager.cs +++ b/Robust.Client/GameObjects/ClientEntityManager.cs @@ -101,11 +101,33 @@ public override void Dirty(EntityUid uid, IComponent component, MetaDataComponen /// public override void Dirty(Entity ent, MetaDataComponent? meta = null) { - // Client only dirties during prediction + // Client only dirties during prediction if (_gameTiming.InPrediction) base.Dirty(ent, meta); } + public override void DirtyField(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null) + { + // TODO Prediction + // does the client actually need to dirty the field? + // I.e., can't it just dirty the whole component to trigger a reset? + + // Client only dirties during prediction + if (_gameTiming.InPrediction) + base.DirtyField(uid, comp, fieldName, metadata); + } + + public override void DirtyFields(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan fields) + { + // TODO Prediction + // does the client actually need to dirty the field? + // I.e., can't it just dirty the whole component to trigger a reset? + + // Client only dirties during prediction + if (_gameTiming.InPrediction) + base.DirtyFields(uid, comp, meta, fields); + } + /// public override void Dirty(Entity ent, MetaDataComponent? meta = null) { diff --git a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs index 8e7dab6feb5..1c4961c31a9 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs @@ -197,6 +197,10 @@ private void RecursivelyUpdatePhysics(Entity(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null) + public virtual void DirtyField(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null) where T : IComponentDelta { var compReg = ComponentFactory.GetRegistration(CompIdx.Index()); + // TODO + // consider storing this on MetaDataComponent? + // We alsready store other dirtying information there anyways, and avoids having to fetch the registration. if (!compReg.NetworkedFieldLookup.TryGetValue(fieldName, out var idx)) { _sawmill.Error($"Tried to dirty delta field {fieldName} on {ToPrettyString(uid)} that isn't implemented."); @@ -54,6 +58,25 @@ public void DirtyField(EntityUid uid, T comp, string fieldName, MetaDataCompo comp.LastModifiedFields[idx] = curTick; Dirty(uid, comp, metadata); } + + + public virtual void DirtyFields(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan fields) + where T : IComponentDelta + { + var compReg = ComponentFactory.GetRegistration(CompIdx.Index()); + + var curTick = _gameTiming.CurTick; + foreach (var field in fields) + { + if (!compReg.NetworkedFieldLookup.TryGetValue(field, out var idx)) + _sawmill.Error($"Tried to dirty delta field {field} on {ToPrettyString(uid)} that isn't implemented."); + else + comp.LastModifiedFields[idx] = curTick; + } + + comp.LastFieldUpdate = curTick; + Dirty(uid, comp, meta); + } } /// diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index fa3798c2280..95889c291c7 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1694,7 +1694,6 @@ public bool HasComponent([NotNullWhen(true)] EntityUid? uid) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true) { if (component != null) @@ -1717,7 +1716,7 @@ public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bo return false; } - [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Resolve(ref Entity entity, bool logMissing = true) { return Resolve(entity.Owner, ref entity.Comp, logMissing); diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index a22b3225d51..0075987d002 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -171,6 +171,13 @@ protected void DirtyField(EntityUid uid, T component, string fieldName, MetaD EntityManager.DirtyField(uid, component, fieldName, meta); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void DirtyFields(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan fields) + where T : IComponentDelta + { + EntityManager.DirtyFields(uid, comp, meta); + } + /// /// Marks a component as dirty. This also implicitly dirties the entity this component belongs to. /// diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 6a97bea3fb6..e475f2b2e0a 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -139,26 +139,26 @@ private void OnPhysicsHandleState(EntityUid uid, PhysicsComponent component, ref if (args.Current is PhysicsLinearVelocityDeltaState linearState) { - SetLinearVelocity(uid, linearState.LinearVelocity, body: component, manager: manager); + SetLinearVelocity(uid, linearState.LinearVelocity, dirty: false, body: component, manager: manager); } else if (args.Current is PhysicsVelocityDeltaState velocityState) { - SetLinearVelocity(uid, velocityState.LinearVelocity, body: component, manager: manager); - SetAngularVelocity(uid, velocityState.AngularVelocity, body: component, manager: manager); + SetLinearVelocity(uid, velocityState.LinearVelocity, dirty: false, body: component, manager: manager); + SetAngularVelocity(uid, velocityState.AngularVelocity, dirty: false, body: component, manager: manager); } else if (args.Current is PhysicsComponentState newState) { - SetSleepingAllowed(uid, component, newState.SleepingAllowed); - SetFixedRotation(uid, newState.FixedRotation, body: component); - SetCanCollide(uid, newState.CanCollide, body: component); + SetSleepingAllowed(uid, component, newState.SleepingAllowed, dirty: false); + SetFixedRotation(uid, newState.FixedRotation, body: component, dirty: false); + SetCanCollide(uid, newState.CanCollide, body: component, dirty: false); component.BodyStatus = newState.Status; - SetLinearVelocity(uid, newState.LinearVelocity, body: component, manager: manager); - SetAngularVelocity(uid, newState.AngularVelocity, body: component, manager: manager); + SetLinearVelocity(uid, newState.LinearVelocity, dirty: false, body: component, manager: manager); + SetAngularVelocity(uid, newState.AngularVelocity, dirty: false, body: component, manager: manager); SetBodyType(uid, newState.BodyType, manager, component); - SetFriction(uid, component, newState.Friction); - SetLinearDamping(uid, component, newState.LinearDamping); - SetAngularDamping(uid, component, newState.AngularDamping); + SetFriction(uid, component, newState.Friction, dirty: false); + SetLinearDamping(uid, component, newState.LinearDamping, dirty: false); + SetAngularDamping(uid, component, newState.AngularDamping, dirty: false); component.Force = newState.Force; component.Torque = newState.Torque; } @@ -270,29 +270,12 @@ public void DestroyContacts(PhysicsComponent body) /// public void ResetDynamics(EntityUid uid, PhysicsComponent body, bool dirty = true) { - if (body.Torque != 0f) - { - body.Torque = 0f; - DirtyField(uid, body, nameof(PhysicsComponent.Torque)); - } - - if (body.AngularVelocity != 0f) - { - body.AngularVelocity = 0f; - DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity)); - } - - if (body.Force != Vector2.Zero) - { - body.Force = Vector2.Zero; - DirtyField(uid, body, nameof(PhysicsComponent.Force)); - } - - if (body.LinearVelocity != Vector2.Zero) - { - body.LinearVelocity = Vector2.Zero; - DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity)); - } + body.Torque = 0f; + body.AngularVelocity = 0f; + body.Force = Vector2.Zero; + body.LinearVelocity = Vector2.Zero; + if (dirty) + DirtyFields(uid, body, null, nameof(PhysicsComponent.Torque), nameof(PhysicsComponent.AngularVelocity), nameof(PhysicsComponent.Force), nameof(PhysicsComponent.LinearVelocity)); } public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null) @@ -397,7 +380,8 @@ public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, Fi return false; body.AngularVelocity = value; - DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity)); return true; } @@ -423,7 +407,9 @@ public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true return false; body.LinearVelocity = velocity; - DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity)); + return true; } @@ -433,7 +419,8 @@ public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, return; body.AngularDamping = value; - DirtyField(uid, body, nameof(PhysicsComponent.AngularDamping)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.AngularDamping)); } public void SetLinearDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) @@ -442,7 +429,8 @@ public void SetLinearDamping(EntityUid uid, PhysicsComponent body, float value, return; body.LinearDamping = value; - DirtyField(uid, body, nameof(PhysicsComponent.LinearDamping)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.LinearDamping)); } [Obsolete("Use SetAwake with EntityUid")] @@ -518,32 +506,27 @@ public void SetBodyType(EntityUid uid, BodyType value, FixturesComponent? manage body.BodyType = value; ResetMassData(uid, manager, body); + body.Force = Vector2.Zero; + body.Torque = 0f; + if (body.BodyType == BodyType.Static) { SetAwake((uid, body), false); - if (body.LinearVelocity != Vector2.Zero) - { - body.LinearVelocity = Vector2.Zero; - DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity)); - } + body.LinearVelocity = Vector2.Zero; + body.AngularVelocity = 0f; - if (body.AngularVelocity != 0f) - { - body.AngularVelocity = 0f; - DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity)); - } + DirtyFields(uid, body, null, + nameof(PhysicsComponent.LinearVelocity), + nameof(PhysicsComponent.AngularVelocity), + nameof(PhysicsComponent.Force), + nameof(PhysicsComponent.Torque)); } // Even if it's dynamic if it can't collide then don't force it awake. else if (body.CanCollide) { SetAwake((uid, body), true); - } - - if (body.Torque != 0f) - { - body.Torque = 0f; - DirtyField(uid, body, nameof(PhysicsComponent.Torque)); + DirtyFields(uid, body, null, nameof(PhysicsComponent.Force), nameof(PhysicsComponent.Torque)); } _broadphase.RegenerateContacts(uid, body, manager, xform); @@ -561,7 +544,8 @@ public void SetBodyStatus(EntityUid uid, PhysicsComponent body, BodyStatus statu return; body.BodyStatus = status; - DirtyField(uid, body, nameof(PhysicsComponent.BodyStatus)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.BodyStatus)); } /// @@ -612,7 +596,10 @@ public bool SetCanCollide( var ev = new CollisionChangeEvent(uid, body, value); RaiseLocalEvent(ref ev); } - DirtyField(uid, body, nameof(PhysicsComponent.CanCollide)); + + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.CanCollide)); + return value; } @@ -622,13 +609,10 @@ public void SetFixedRotation(EntityUid uid, bool value, bool dirty = true, Fixtu return; body.FixedRotation = value; - DirtyField(uid, body, nameof(PhysicsComponent.FixedRotation)); + body.AngularVelocity = 0.0f; - if (body.AngularVelocity != 0f) - { - body.AngularVelocity = 0.0f; - DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity)); - } + if (dirty) + DirtyFields(uid, body, null, nameof(PhysicsComponent.FixedRotation), nameof(PhysicsComponent.AngularVelocity)); ResetMassData(uid, manager: manager, body: body); } @@ -639,7 +623,8 @@ public void SetFriction(EntityUid uid, PhysicsComponent body, float value, bool return; body._friction = value; - DirtyField(uid, body, nameof(PhysicsComponent.Friction)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.Friction)); } public void SetInertia(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) @@ -678,7 +663,8 @@ public void SetSleepingAllowed(EntityUid uid, PhysicsComponent body, bool value, SetAwake((uid, body), true); body.SleepingAllowed = value; - DirtyField(uid, body, nameof(PhysicsComponent.SleepingAllowed)); + if (dirty) + DirtyField(uid, body, nameof(PhysicsComponent.SleepingAllowed)); } public void SetSleepTime(PhysicsComponent body, float value) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 848f44fbccc..4aa69d3d67f 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -83,6 +83,13 @@ public override void Initialize() _physicsReg = EntityManager.ComponentFactory.GetRegistration(CompIdx.Index()); + // TODO PHYSICS STATE + // Consider condensing the possible fields into just Linear velocity, angular velocity, and "Other" + // Or maybe even just "velocity" & "other" + // Then get-state doesn't have to iterate over a 10-element array. + // And it simplifies the DirtyField calls. + // Though I guess combining fixtures & physics will complicate it a bit more again. + // If you update this then update the delta state + GetState + HandleState! EntityManager.ComponentFactory.RegisterNetworkedFields(_physicsReg, nameof(PhysicsComponent.CanCollide), From 27938184421ecb67d704a1e0b378cae39754f24e Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sun, 19 Jan 2025 20:53:20 +1300 Subject: [PATCH 2/2] spaaaaaaace --- Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs b/Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs index e2b06c8b57b..77210165d7f 100644 --- a/Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs +++ b/Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs @@ -59,7 +59,6 @@ public virtual void DirtyField(EntityUid uid, T comp, string fieldName, MetaD Dirty(uid, comp, metadata); } - public virtual void DirtyFields(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan fields) where T : IComponentDelta {