From da88d9f257241e3e3d7e7221d27c13e2aebbab6b Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Wed, 4 Nov 2026 14:45:36 +0100 Subject: [PATCH] Fix vOp operations truncating the target vector when an operand is empty --- .../statecontainer/MechanicalObject.inl | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/Sofa/Component/StateContainer/src/sofa/component/statecontainer/MechanicalObject.inl b/Sofa/Component/StateContainer/src/sofa/component/statecontainer/MechanicalObject.inl index 5ab77c64a48..c50ef0ac8b2 100644 --- a/Sofa/Component/StateContainer/src/sofa/component/statecontainer/MechanicalObject.inl +++ b/Sofa/Component/StateContainer/src/sofa/component/statecontainer/MechanicalObject.inl @@ -1602,7 +1602,8 @@ bool vOp_vf(MechanicalObject& self, core::VecId v, Real_t { if constexpr (requires(core::StateVecType_t vc, Real_t fc){vc[0] *= fc;}) { - auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); + // write accessor (instead of write-only accessor) because content must be updated if dirty before the multiplication + auto vv = helper::getWriteAccessor(*self.write(core::TVecId(v))); for (unsigned int i = 0; i < vv.size(); ++i) { vv[i] *= k; @@ -1619,9 +1620,14 @@ bool vOp_vbf(MechanicalObject& self, core::VecId v, core::ConstVecId { // Read accessors are created before the write-only accessor on purpose: see vOp_va. auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); + if (vb.empty()) + return true; + + // write-only accessor (instead of write-accessor) because content is irrelevant and will be overwritten auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); - vv.resize(vb.size()); - for (unsigned int i = 0; i < vv.size(); ++i) + if (vv.size() < vb.size()) + vv.resize(vb.size()); + for (unsigned int i = 0; i < vb.size(); ++i) { vv[i] = vb[i] * k; } @@ -1641,6 +1647,8 @@ bool vOp_va(MechanicalObject& self, core::VecId v, core::ConstVecId b // copy-on-write shared_ptr and would free the buffer captured by a write accessor // opened earlier. This matters in particular when v == b (self-copy of a linked Data). auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); + + // write-only accessor (instead of write-accessor) because content is irrelevant and will be overwritten auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); vv.wref() = vb.ref(); return true; @@ -1659,9 +1667,14 @@ bool vOp_vb(MechanicalObject& self, core::VecId v, core::ConstVecId b { // Read accessors are created before the write-only accessor on purpose: see vOp_va. auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); - auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); - vv.resize(vb.size()); - for (unsigned int i = 0; i < vv.size(); ++i) + if (vb.empty()) + return true; + + // write accessor (instead of write-only accessor) because content must be updated if dirty before the accumulation + auto vv = helper::getWriteAccessor(*self.write(core::TVecId(v))); + if (vv.size() < vb.size()) + vv.resize(vb.size()); + for (unsigned int i = 0; i < vb.size(); ++i) { vv[i] += vb[i]; } @@ -1677,9 +1690,12 @@ bool vOp_avf(MechanicalObject& self, core::VecId v, core::ConstVecId { // Read accessors are created before the write-only accessor on purpose: see vOp_va. auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); - auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); - vv.resize(vb.size()); - for (unsigned int i = 0; i < vv.size(); ++i) + + // write accessor (instead of write-only accessor) because content must be updated if dirty before the accumulation + auto vv = helper::getWriteAccessor(*self.write(core::TVecId(v))); + if (vv.size() < vb.size()) + vv.resize(vb.size()); + for (unsigned int i = 0; i < vb.size(); ++i) { vv[i] *= k; vv[i] += vb[i]; @@ -1696,9 +1712,14 @@ bool vOp_v_inc_bf(MechanicalObject& self, core::VecId v, core::ConstV { // Read accessors are created before the write-only accessor on purpose: see vOp_va. auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); - auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); - vv.resize(vb.size()); - for (unsigned int i = 0; i < vv.size(); ++i) + if (vb.empty()) + return true; + + // write accessor (instead of write-only accessor) because content must be updated if dirty before the accumulation + auto vv = helper::getWriteAccessor(*self.write(core::TVecId(v))); + if (vv.size() < vb.size()) + vv.resize(vb.size()); + for (unsigned int i = 0; i < vb.size(); ++i) { vv[i] += vb[i] * k; } @@ -1716,10 +1737,19 @@ bool vOp_vab(MechanicalObject& self, core::VecId v, core::ConstVecId { // Read accessors are created before the write-only accessor on purpose: see vOp_va. auto va = helper::getReadAccessor(*self.read(core::TVecId(a))); + if (va.empty()) + return true; + auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); + if (vb.empty()) + return true; + + // write-only accessor (instead of write-accessor) because content is irrelevant and will be overwritten auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); - vv.resize(vb.size()); - for (unsigned int i = 0; i < vv.size(); ++i) + const auto size = std::min(va.size(), vb.size()); + if (vv.size() < size) + vv.resize(size); + for (unsigned int i = 0; i < size; ++i) { vv[i] = va[i] + vb[i]; } @@ -1735,10 +1765,19 @@ bool vOp_vabf(MechanicalObject& self, core::VecId v, core::ConstVecId { // Read accessors are created before the write-only accessor on purpose: see vOp_va. auto va = helper::getReadAccessor(*self.read(core::TVecId(a))); + if (va.empty()) + return true; + auto vb = helper::getReadAccessor(*self.read(core::TVecId(b))); + if (vb.empty()) + return true; + + // write-only accessor (instead of write-accessor) because content is irrelevant and will be overwritten auto vv = helper::getWriteOnlyAccessor(*self.write(core::TVecId(v))); - vv.resize(vb.size()); - for (unsigned int i = 0; i < vv.size(); ++i) + const auto size = std::min(va.size(), vb.size()); + if (vv.size() < size) + vv.resize(size); + for (unsigned int i = 0; i < size; ++i) { vv[i] = va[i] + vb[i] * k; }