Skip to content
Merged
140 changes: 137 additions & 3 deletions core/PhysiCell_cell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,15 @@ Cell_State::Cell_State()
neighbors.resize(0);
spring_attachments.resize(0);

attacked_by.resize(0);

orientation.resize( 3 , 0.0 );

simple_pressure = 0.0;

attached_cells.clear();
spring_attachments.clear();
attacked_by.clear();

number_of_nuclei = 1;

Expand Down Expand Up @@ -457,6 +460,8 @@ Cell::~Cell()
{
// release any attached cells (as of 1.7.2 release)
this->remove_all_attached_cells();
this->remove_all_attackers();
this->remove_self_from_attacked();
// 1.11.0
this->remove_all_spring_attachments();

Expand Down Expand Up @@ -551,6 +556,10 @@ Cell* Cell::divide( )

// make sure ot remove adhesions
remove_all_attached_cells();
// the springs below include the one holding an attack together, so end the
// attack rather than leave it spring-less
remove_all_attackers();
remove_self_from_attacked();
remove_all_spring_attachments();

// version 1.10.3:
Expand Down Expand Up @@ -651,6 +660,7 @@ Cell* Cell::divide( )
set_total_volume(phenotype.volume.total);

// child->set_phenotype( phenotype );
// pAttackTarget was cleared above, so the daughter inherits a NULL one
child->phenotype = phenotype;

if (child->phenotype.intracellular){
Expand Down Expand Up @@ -1144,6 +1154,14 @@ void Cell::convert_to_cell_definition( Cell_Definition& cd )
Geometry cell_geometry = phenotype.geometry;
Molecular cell_molecular = phenotype.molecular;
Custom_Cell_Data cell_custom_data = custom_data;

// pAttackTarget is phenotype state, and the assignment below replaces it, so
// end this cell's own attack. attacked_by and the spring are in state, which
// transformation preserves, so attacks against this cell continue.
remove_self_from_attacked();
// should we also remove all attackers? That would be a change in behavior, so for now we don't.
// remove_all_attackers();

// use the cell defaults;
type = cd.type;
type_name = cd.name;
Expand Down Expand Up @@ -1210,6 +1228,8 @@ void delete_cell( int index )

// release any attached cells (as of 1.7.2 release)
pDeleteMe->remove_all_attached_cells();
pDeleteMe->remove_all_attackers();
pDeleteMe->remove_self_from_attacked();
// 1.11.0
pDeleteMe->remove_all_spring_attachments();

Expand Down Expand Up @@ -1244,6 +1264,8 @@ void delete_cell_original( int index ) // before June 11, 2020

// release any attached cells (as of 1.7.2 release)
(*all_cells)[index]->remove_all_attached_cells();
(*all_cells)[index]->remove_all_attackers();
(*all_cells)[index]->remove_self_from_attacked();
// 1.11.0
(*all_cells)[index]->remove_all_spring_attachments();

Expand Down Expand Up @@ -1473,6 +1495,8 @@ void Cell::ingest_cell( Cell* pCell_to_eat )
// things that have their own thread safety
pCell_to_eat->flag_for_removal();
pCell_to_eat->remove_all_attached_cells();
pCell_to_eat->remove_all_attackers();
pCell_to_eat->remove_self_from_attacked();
pCell_to_eat->remove_all_spring_attachments();

return;
Expand Down Expand Up @@ -1641,6 +1665,8 @@ void Cell::fuse_cell( Cell* pCell_to_fuse )
// things that have their own thread safety
pCell_to_fuse->flag_for_removal();
pCell_to_fuse->remove_all_attached_cells();
pCell_to_fuse->remove_all_attackers();
pCell_to_fuse->remove_self_from_attacked();
pCell_to_fuse->remove_all_spring_attachments();

return;
Expand All @@ -1652,12 +1678,12 @@ void Cell::lyse_cell( void )
if( phenotype.volume.total < 1e-15 )
{ return; }

// mark it as dead (before flagging, to match ingest_cell and fuse_cell)
phenotype.death.dead = true;

// flag for removal
flag_for_removal(); // should be safe now

// mark it as dead
phenotype.death.dead = true;

// set secretion and uptake to zero
phenotype.secretion.set_all_secretion_to_zero( );
phenotype.secretion.set_all_uptake_to_zero( );
Expand All @@ -1672,6 +1698,9 @@ void Cell::lyse_cell( void )
// remove all adhesions

remove_all_attached_cells();
remove_all_attackers();
remove_self_from_attacked();
remove_all_spring_attachments();

// set volume to zero
set_total_volume( 0.0 );
Expand Down Expand Up @@ -3457,6 +3486,78 @@ void Cell::remove_all_spring_attachments( void )
return;
}

void Cell::add_attacker( Cell* pAddMe )
{
#pragma omp critical
{
if( std::find( state.attacked_by.begin() , state.attacked_by.end() , pAddMe )
== state.attacked_by.end() )
{ state.attacked_by.push_back( pAddMe ); }
}
return;
}

void Cell::remove_attacker( Cell* pRemoveMe )
{
#pragma omp critical
{
auto search = std::find( state.attacked_by.begin() , state.attacked_by.end() , pRemoveMe );
if( search != state.attacked_by.end() )
{
// copy last entry over it, then shrink by one
*search = state.attacked_by.back();
state.attacked_by.pop_back();
}
}
return;
}

void Cell::remove_all_attackers( void )
{
// Swap the list out under the same lock add_attacker / remove_attacker use, then
// walk the local copy. Holding the lock across the loop is not an option:
// detach_cells_as_spring() takes the same unnamed critical and OpenMP criticals
// are not reentrant, so that would deadlock.
std::vector<Cell*> attackers;
#pragma omp critical
{ attackers.swap( state.attacked_by ); }

for( int i = 0; i < attackers.size() ; i++ )
{
Cell* pAttacker = attackers[i];
if( pAttacker->phenotype.cell_interactions.pAttackTarget == this )
{ pAttacker->phenotype.cell_interactions.pAttackTarget = NULL; }
detach_cells_as_spring( pAttacker , this );
Comment thread
drbergman marked this conversation as resolved.
}
return;
}
Comment thread
drbergman marked this conversation as resolved.

bool Cell::is_attacked_by( Cell* pCell )
{
bool found = false;
#pragma omp critical
{
found = std::find( state.attacked_by.begin() , state.attacked_by.end() , pCell )
!= state.attacked_by.end();
}
return found;
}

void Cell::remove_self_from_attacked( void )
{
Cell* pTarget = phenotype.cell_interactions.pAttackTarget;
if( pTarget == NULL )
{ return; }
phenotype.cell_interactions.pAttackTarget = NULL;
pTarget->remove_attacker( this );
// mutual attackers share one spring, so only drop it once both are done.
// "does pTarget attack me?" is asked of our own attacked_by, which we can lock,
// rather than by reading pTarget's pAttackTarget unsynchronised.
if( !is_attacked_by( pTarget ) )
{ detach_cells_as_spring( this , pTarget ); }
return;
}


void attach_cells( Cell* pCell_1, Cell* pCell_2 )
{
Expand All @@ -3483,6 +3584,39 @@ void detach_cells_as_spring( Cell* pCell_1 , Cell* pCell_2 )
{
pCell_1->detach_cell_as_spring( pCell_2 );
pCell_2->detach_cell_as_spring( pCell_1 );
return;
}

void begin_attack( Cell* pAttacker , Cell* pTarget )
{
// no self-attack, and nothing to do without both ends
if( pAttacker == NULL || pTarget == NULL || pAttacker == pTarget )
{ return; }

// a cell attacks at most one target at a time. if it is already attacking
// something, close that attack out cleanly rather than orphaning the link.
if( pAttacker->phenotype.cell_interactions.pAttackTarget != NULL )
{ pAttacker->remove_self_from_attacked(); }

pAttacker->phenotype.cell_interactions.pAttackTarget = pTarget;
pTarget->add_attacker( pAttacker );
// spring-link these cells
attach_cells_as_spring( pAttacker , pTarget );
return;
}

void end_attack( Cell* pAttacker , Cell* pTarget )
{
if( pAttacker == NULL || pTarget == NULL )
{ return; }

if( pAttacker->phenotype.cell_interactions.pAttackTarget == pTarget )
{ pAttacker->phenotype.cell_interactions.pAttackTarget = NULL; }
pTarget->remove_attacker( pAttacker );
// two cells can attack each other, and attach_cell_as_spring dedupes, so the
// two attacks share ONE spring. Only drop it once nobody still needs it.
if( !pAttacker->is_attacked_by( pTarget ) )
{ detach_cells_as_spring( pAttacker , pTarget ); }
return;
}

Expand Down
16 changes: 16 additions & 0 deletions core/PhysiCell_cell.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ class Cell_State
std::vector<Cell*> attached_cells;
std::vector<Cell*> spring_attachments;

// reverse of phenotype.cell_interactions.pAttackTarget. in state, not
// phenotype, so it survives convert_to_cell_definition()
std::vector<Cell*> attacked_by;

std::vector<Cell*> neighbors;
std::vector<double> orientation;

Expand Down Expand Up @@ -239,6 +243,13 @@ class Cell : public Basic_Agent
void detach_cell_as_spring( Cell* pRemoveMe ); // done
void remove_all_spring_attachments( void ); // done

void add_attacker( Cell* pAddMe ); // pAddMe has started attacking me
void remove_attacker( Cell* pRemoveMe ); // pRemoveMe has stopped attacking me
// call these wherever remove_all_spring_attachments() is called
void remove_all_attackers( void ); // everyone attacking me stops
void remove_self_from_attacked( void ); // I stop attacking whomever I attack
bool is_attacked_by( Cell* pCell ); // is pCell currently attacking me?

// I want to eventually deprecate this, by ensuring that
// critical BioFVM and PhysiCell data elements are synced when they are needed

Expand Down Expand Up @@ -296,6 +307,11 @@ void detach_cells( Cell* pCell_1 , Cell* pCell_2 );
void attach_cells_as_spring( Cell* pCell_1, Cell* pCell_2 );
void detach_cells_as_spring( Cell* pCell_1 , Cell* pCell_2 );

// maintain pAttackTarget, attacked_by, and the spring together. use these
// rather than writing pAttackTarget directly.
void begin_attack( Cell* pAttacker , Cell* pTarget );
void end_attack( Cell* pAttacker , Cell* pTarget );


std::vector<Cell*> find_nearby_cells( Cell* pCell ); // new in 1.8.0
std::vector<Cell*> find_nearby_interacting_cells( Cell* pCell ); // new in 1.8.0
Expand Down
10 changes: 4 additions & 6 deletions core/PhysiCell_standard_models.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1265,16 +1265,16 @@ void standard_cell_cell_interactions( Cell* pCell, Phenotype& phenotype, double
{
if( UniformRandom() < probability )
{
pCell->phenotype.cell_interactions.pAttackTarget = pTarget;
// sets pAttackTarget, registers pCell in pTarget's
// attacked_by, and spring-links the two
begin_attack( pCell , pTarget );
attacked = true;
/*
std::cout << "********* ********* ******** start atack **** " << PhysiCell_globals.current_time << std::endl;
std::cout
<< "attack duration: " << pCell->phenotype.cell_interactions.attack_duration << " "
<< "attack damage rate: " << pCell->phenotype.cell_interactions.attack_damage_rate << std::endl;
*/
// spring-link these cells
attach_cells_as_spring(pCell,pTarget);
}
}

Expand Down Expand Up @@ -1341,9 +1341,7 @@ void standard_cell_cell_interactions( Cell* pCell, Phenotype& phenotype, double
<< "damage delivered: " << pCell->phenotype.cell_interactions.total_damage_delivered << std::endl;
*/

detach_cells_as_spring(pCell,pTarget);

pCell->phenotype.cell_interactions.pAttackTarget = NULL;
end_attack( pCell , pTarget );
}
}

Expand Down
5 changes: 4 additions & 1 deletion modules/PhysiCell_MultiCellDS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2196,7 +2196,10 @@ int recreate_sim_state(std::string filename, Microenvironment& M,
{
if (cell->ID == pair.second)
{
(pair.first)->phenotype.cell_interactions.pAttackTarget = cell;
// restore all three records, not just the pointer: the spring
// is not serialised, so without this a resumed run starts with
// an attack that has no spring behind it
begin_attack( pair.first , cell );
if (debug_print)
{ std::cout << " cell ID=" << (pair.first)->ID << " attacking cell ID=" << cell->ID << std::endl; }
break;
Expand Down
Loading