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

Allowing for derived Cell objects, defined by cell types #153

Merged
merged 2 commits into from
Jul 26, 2023

Conversation

vincent-noel
Copy link
Collaborator

@vincent-noel vincent-noel commented Apr 21, 2023

I had proposed a PR some time ago already around this idea, but then I wasn't sure it was so important and it ended up being closed.
With the new implementation of PhysiMESS, I wanted to revive it, as I think it will enable things which are not possible otherwise (mostly at destruction), and simplify our life a lot for designing extensions of the Cell class.

The idea is that for PhysiMESS (and possibly other addons) we need to add new variables to the Cell. We can do this by having our own data structure, like :

std::map<Cell*, SomeObject> physimess_cell_object;

which is how I defined it for now. And it works, you have to initialize it after cell instantiation, and it's not so bad. But at deletion it's a problem, because we can never clean it at the exact same time as the Cell deletion.
But with derived class, we would have our own constructor/destructors, so it would simplify instantiation and would allow proper destruction.
Also, important to note, it's a non-breaking change, everything would keep working the same way as before for those who don't want to use derived classes of Cell.

The basic workflow would be the following : First, we defined our specialized class. Here I won't go into details, you get the idea.

class PhysiMESS_Cell : public Cell 
{ ... }
class PhysiMESS_Fibre : public Cell 
{ ... }

Then we need to be sure that at cell creation, we are instantiating the proper Class. To do this, we create new custom functions, and we assign them to a function pointer in the Cell_Functions class, depending on the cell type

Cell* instanciate_physimess_cell () { return new PhysiMeSS_Cell; }
Cell* instanciate_physimess_fibre () { return new PhysiMeSS_Fibre; }

create_cell_type() 
{
...
initialize_cell_definitions_from_pugixml(); 
...
// Default cell, in our case a PhysiMESS cell 
cell_defaults.functions.instantiate_cell = instantiate_physimess_cell;
...
// PhysiMeSS fibre
pCD = find_cell_definition( "fibre"); 
pCD->functions.instantiate_cell = instantiate_physimess_fibre;
...
}

Then, either in setup_tissue, or in load_cells_from_pugixml, when we would use

create_cell(cell_definition);

it would automatically call the proper constructor via the instantiate_cell function pointer defined in the cell's definition Cell_Functions class. If this function pointer is NULL, then it will call a standard one.
The way it works is that create_cell will now have an optional argument, which is a function pointer to an instantiate functions :

Cell* standard_instantiate_cell() { return new Cell; }

Cell* create_cell( Cell* (*custom_instantiate)() = NULL );  
Cell* create_cell( Cell* (*custom_instantiate)())
{
	Cell* pNew; 

	if (custom_instantiate) {
		pNew = custom_instantiate();
	} else {
		pNew = standard_instantiate_cell();
	}
        ...
}

So the old create_cell() will still work and create a default_cell, but if we want a custom function we can put it as argument.
And create_cell(Cell_Definition) can now use it :

Cell* create_cell( Cell_Definition& cd );
Cell* create_cell( Cell_Definition& cd )
{
	Cell* pNew = create_cell(cd.functions.instantiate_cell); 
        ...
}

At division, we would instantiate the daughter cell using the same instantiation function of the mother cell :

Cell* child = create_cell(functions.instantiate_cell);

And last detail, to make sure that at destruction of our Cell* the potential derived destructor is called, we need to declare the Cell destructor as virtual, as well as the Basic_Agent destructor.

Let me know what you think ! I will probably write a new PhysiMESS version with this addition next month, to be able to have the example of the two approaches, which will probably help the discussion.

@vincent-noel vincent-noel changed the base branch from master to development May 16, 2023 09:31
@josephabrams
Copy link

My current (admittedly clunky) workaround to this issue is to use an encapsulated Cell object with its own separate "create custom object" function, but for obvious reasons, the above version would be an improvement. Similarly, I would love to be able to do this with other phenotypes that have functionality not built into the base class.
My example workaround is something along these lines:

`class Custom_Cell
{

private:

public:
std::vector additional_cell_states;
void additional_methods;
Cell* m_my_pCell;
};
Custom_Cell* create_custom_cell(Cell* pCell);
extern std::vector <Custom_Cell*> custom_cell_by_pCell_index_for_accessing;
`

@MathCancer MathCancer merged commit ffe584d into MathCancer:development Jul 26, 2023
@vincent-noel vincent-noel deleted the feat/custom_cell branch July 27, 2023 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants