progress on multithreaded Panda

This commit is contained in:
David Rose 2011-09-04 00:11:57 +00:00
parent 08693413e8
commit c3d8f81581
7 changed files with 238 additions and 136 deletions

View File

@ -188,7 +188,13 @@ ConfigVariableBool auto_break_cycles
PRC_DESC("Set this true to automatically detect and break reference-count " PRC_DESC("Set this true to automatically detect and break reference-count "
"cycles in the TransformState and RenderState caches. When this " "cycles in the TransformState and RenderState caches. When this "
"is false, you must explicitly call TransformState.clear_cache() " "is false, you must explicitly call TransformState.clear_cache() "
"from time to time to prevent gradual memory bloat.")); "from time to time to prevent gradual memory bloat. This has "
"no meaning when garbage-collect-states is true."));
ConfigVariableBool garbage_collect_states
("garbage-collect-states", false,
PRC_DESC("This temporary config variable is used for development only. "
"Do not set!"));
ConfigVariableBool transform_cache ConfigVariableBool transform_cache
("transform-cache", true, ("transform-cache", true,

View File

@ -44,6 +44,7 @@ extern ConfigVariableBool compose_componentwise;
extern ConfigVariableBool uniquify_matrix; extern ConfigVariableBool uniquify_matrix;
extern ConfigVariableBool paranoid_const; extern ConfigVariableBool paranoid_const;
extern ConfigVariableBool auto_break_cycles; extern ConfigVariableBool auto_break_cycles;
extern ConfigVariableBool garbage_collect_states;
extern ConfigVariableBool transform_cache; extern ConfigVariableBool transform_cache;
extern ConfigVariableBool state_cache; extern ConfigVariableBool state_cache;
extern ConfigVariableBool uniquify_transforms; extern ConfigVariableBool uniquify_transforms;

View File

@ -144,6 +144,11 @@ cull_callback(CullTraverser *, const CullTraverserData &) const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool RenderAttrib:: bool RenderAttrib::
unref() const { unref() const {
// This is flawed, but this is development only.
if (garbage_collect_states) {
return ReferenceCount::unref();
}
// We always have to grab the lock, since we will definitely need to // We always have to grab the lock, since we will definitely need to
// be holding it if we happen to drop the reference count to 0. // be holding it if we happen to drop the reference count to 0.
LightReMutexHolder holder(*_attribs_lock); LightReMutexHolder holder(*_attribs_lock);

View File

@ -419,11 +419,9 @@ compose(const RenderState *other) const {
return this; return this;
} }
#ifndef NDEBUG
if (!state_cache) { if (!state_cache) {
return do_compose(other); return do_compose(other);
} }
#endif // NDEBUG
LightReMutexHolder holder(*_states_lock); LightReMutexHolder holder(*_states_lock);
@ -516,11 +514,9 @@ invert_compose(const RenderState *other) const {
return make_empty(); return make_empty();
} }
#ifndef NDEBUG
if (!state_cache) { if (!state_cache) {
return do_invert_compose(other); return do_invert_compose(other);
} }
#endif // NDEBUG
LightReMutexHolder holder(*_states_lock); LightReMutexHolder holder(*_states_lock);
@ -706,6 +702,11 @@ adjust_all_priorities(int adjustment) const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool RenderState:: bool RenderState::
unref() const { unref() const {
// This is flawed, but this is development only.
if (garbage_collect_states) {
return ReferenceCount::unref();
}
// We always have to grab the lock, since we will definitely need to // We always have to grab the lock, since we will definitely need to
// be holding it if we happen to drop the reference count to 0. // be holding it if we happen to drop the reference count to 0.
LightReMutexHolder holder(*_states_lock); LightReMutexHolder holder(*_states_lock);
@ -1460,11 +1461,9 @@ CPT(RenderState) RenderState::
return_unique(RenderState *state) { return_unique(RenderState *state) {
nassertr(state != (RenderState *)NULL, state); nassertr(state != (RenderState *)NULL, state);
#ifndef NDEBUG
if (!state_cache) { if (!state_cache) {
return state; return state;
} }
#endif
#ifndef NDEBUG #ifndef NDEBUG
if (paranoid_const) { if (paranoid_const) {

View File

@ -579,10 +579,6 @@ set_shear2d(float shear) const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
CPT(TransformState) TransformState:: CPT(TransformState) TransformState::
compose(const TransformState *other) const { compose(const TransformState *other) const {
// This method isn't strictly const, because it updates the cache,
// but we pretend that it is because it's only a cache which is
// transparent to the rest of the interface.
// We handle identity as a trivial special case. // We handle identity as a trivial special case.
if (is_identity()) { if (is_identity()) {
return other; return other;
@ -599,72 +595,37 @@ compose(const TransformState *other) const {
return other; return other;
} }
#ifndef NDEBUG
if (!transform_cache) { if (!transform_cache) {
return do_compose(other); return do_compose(other);
} }
#endif // NDEBUG
LightReMutexHolder holder(*_states_lock);
// Is this composition already cached? // Is this composition already cached?
int index = _composition_cache.find(other); CPT(TransformState) result;
if (index != -1) { {
Composition &comp = ((TransformState *)this)->_composition_cache.modify_data(index); LightReMutexHolder holder(*_states_lock);
if (comp._result == (const TransformState *)NULL) { int index = _composition_cache.find(other);
// Well, it wasn't cached already, but we already had an entry if (index != -1) {
// (probably created for the reverse direction), so use the same const Composition &comp = _composition_cache.get_data(index);
// entry to store the new result. result = comp._result;
CPT(TransformState) result = do_compose(other); }
comp._result = result; if (result != (TransformState *)NULL) {
_cache_stats.inc_hits();
if (result != (const TransformState *)this) {
// See the comments below about the need to up the reference
// count only when the result is not the same as this.
result->cache_ref();
}
} }
// Here's the cache!
_cache_stats.inc_hits();
return comp._result;
}
_cache_stats.inc_misses();
// We need to make a new cache entry, both in this object and in the
// other object. We make both records so the other TransformState
// object will know to delete the entry from this object when it
// destructs, and vice-versa.
// The cache entry in this object is the only one that indicates the
// result; the other will be NULL for now.
CPT(TransformState) result = do_compose(other);
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(_composition_cache.get_size() == 0);
((TransformState *)this)->_composition_cache[other]._result = result;
if (other != this) {
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(other->_composition_cache.get_size() == 0);
((TransformState *)other)->_composition_cache[this]._result = NULL;
} }
if (result != (const TransformState *)this) { if (result != (TransformState *)NULL) {
// If the result of compose() is something other than this, // Success!
// explicitly increment the reference count. We have to be sure return result;
// to decrement it again later, when the composition entry is
// removed from the cache.
result->cache_ref();
// (If the result was just this again, we still store the
// result, but we don't increment the reference count, since
// that would be a self-referential leak.)
} }
_cache_stats.maybe_report("TransformState"); // Not in the cache. Compute a new result. It's important that we
// don't hold the lock while we do this, or we lose the benefit of
// parallelization.
result = do_compose(other);
return result; // It's OK to cast away the constness of this pointer, because the
// cache is a transparent property of the class.
return ((TransformState *)this)->store_compose(other, result);
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -704,69 +665,38 @@ invert_compose(const TransformState *other) const {
return make_identity(); return make_identity();
} }
#ifndef NDEBUG
if (!transform_cache) { if (!transform_cache) {
return do_invert_compose(other); return do_invert_compose(other);
} }
#endif // NDEBUG
LightReMutexHolder holder(*_states_lock); LightReMutexHolder holder(*_states_lock);
// Is this composition already cached? CPT(TransformState) result;
int index = _invert_composition_cache.find(other); {
if (index != -1) { LightReMutexHolder holder(*_states_lock);
Composition &comp = ((TransformState *)this)->_invert_composition_cache.modify_data(index); int index = _invert_composition_cache.find(other);
if (comp._result == (const TransformState *)NULL) { if (index != -1) {
// Well, it wasn't cached already, but we already had an entry const Composition &comp = _invert_composition_cache.get_data(index);
// (probably created for the reverse direction), so use the same result = comp._result;
// entry to store the new result. }
CPT(TransformState) result = do_invert_compose(other); if (result != (TransformState *)NULL) {
comp._result = result; _cache_stats.inc_hits();
if (result != (const TransformState *)this) {
// See the comments below about the need to up the reference
// count only when the result is not the same as this.
result->cache_ref();
}
} }
// Here's the cache!
_cache_stats.inc_hits();
return comp._result;
}
_cache_stats.inc_misses();
// We need to make a new cache entry, both in this object and in the
// other object. We make both records so the other TransformState
// object will know to delete the entry from this object when it
// destructs, and vice-versa.
// The cache entry in this object is the only one that indicates the
// result; the other will be NULL for now.
CPT(TransformState) result = do_invert_compose(other);
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(_invert_composition_cache.get_size() == 0);
((TransformState *)this)->_invert_composition_cache[other]._result = result;
if (other != this) {
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(other->_invert_composition_cache.get_size() == 0);
((TransformState *)other)->_invert_composition_cache[this]._result = NULL;
} }
if (result != (const TransformState *)this) { if (result != (TransformState *)NULL) {
// If the result of compose() is something other than this, // Success!
// explicitly increment the reference count. We have to be sure return result;
// to decrement it again later, when the composition entry is
// removed from the cache.
result->cache_ref();
// (If the result was just this again, we still store the
// result, but we don't increment the reference count, since
// that would be a self-referential leak.)
} }
return result; // Not in the cache. Compute a new result. It's important that we
// don't hold the lock while we do this, or we lose the benefit of
// parallelization.
result = do_invert_compose(other);
// It's OK to cast away the constness of this pointer, because the
// cache is a transparent property of the class.
return ((TransformState *)this)->store_invert_compose(other, result);
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -781,6 +711,11 @@ invert_compose(const TransformState *other) const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool TransformState:: bool TransformState::
unref() const { unref() const {
// This is flawed, but this is development only.
if (garbage_collect_states) {
return ReferenceCount::unref();
}
// We always have to grab the lock, since we will definitely need to // We always have to grab the lock, since we will definitely need to
// be holding it if we happen to drop the reference count to 0. // be holding it if we happen to drop the reference count to 0.
LightReMutexHolder holder(*_states_lock); LightReMutexHolder holder(*_states_lock);
@ -1526,11 +1461,9 @@ CPT(TransformState) TransformState::
return_unique(TransformState *state) { return_unique(TransformState *state) {
nassertr(state != (TransformState *)NULL, state); nassertr(state != (TransformState *)NULL, state);
#ifndef NDEBUG
if (!transform_cache) { if (!transform_cache) {
return state; return state;
} }
#endif
#ifndef NDEBUG #ifndef NDEBUG
if (paranoid_const) { if (paranoid_const) {
@ -1647,6 +1580,158 @@ do_compose(const TransformState *other) const {
} }
} }
////////////////////////////////////////////////////////////////////
// Function: TransformState::store_compose
// Access: Private
// Description: Stores the result of a composition in the cache.
// Returns the stored result (it may be a different
// object than the one passed in, due to another thread
// having computed the composition first).
////////////////////////////////////////////////////////////////////
CPT(TransformState) TransformState::
store_compose(const TransformState *other, const TransformState *result) {
// Identity should have already been screened.
nassertr(!is_identity(), other);
nassertr(!other->is_identity(), this);
// So should have validity.
nassertr(!is_invalid(), this);
nassertr(!other->is_invalid(), other);
LightReMutexHolder holder(*_states_lock);
// Is this composition already cached?
int index = _composition_cache.find(other);
if (index != -1) {
Composition &comp = _composition_cache.modify_data(index);
if (comp._result == (const TransformState *)NULL) {
// Well, it wasn't cached already, but we already had an entry
// (probably created for the reverse direction), so use the same
// entry to store the new result.
comp._result = result;
if (result != (const TransformState *)this) {
// See the comments below about the need to up the reference
// count only when the result is not the same as this.
result->cache_ref();
}
}
// Here's the cache!
_cache_stats.inc_hits();
return comp._result;
}
_cache_stats.inc_misses();
// We need to make a new cache entry, both in this object and in the
// other object. We make both records so the other TransformState
// object will know to delete the entry from this object when it
// destructs, and vice-versa.
// The cache entry in this object is the only one that indicates the
// result; the other will be NULL for now.
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(_composition_cache.get_size() == 0);
_composition_cache[other]._result = result;
if (other != this) {
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(other->_composition_cache.get_size() == 0);
((TransformState *)other)->_composition_cache[this]._result = NULL;
}
if (result != (TransformState *)this) {
// If the result of do_compose() is something other than this,
// explicitly increment the reference count. We have to be sure
// to decrement it again later, when the composition entry is
// removed from the cache.
result->cache_ref();
// (If the result was just this again, we still store the
// result, but we don't increment the reference count, since
// that would be a self-referential leak.)
}
_cache_stats.maybe_report("TransformState");
return result;
}
////////////////////////////////////////////////////////////////////
// Function: TransformState::store_invert_compose
// Access: Private
// Description: Stores the result of a composition in the cache.
// Returns the stored result (it may be a different
// object than the one passed in, due to another thread
// having computed the composition first).
////////////////////////////////////////////////////////////////////
CPT(TransformState) TransformState::
store_invert_compose(const TransformState *other, const TransformState *result) {
// Identity should have already been screened.
nassertr(!is_identity(), other);
// So should have validity.
nassertr(!is_invalid(), this);
nassertr(!other->is_invalid(), other);
nassertr(other != this, make_identity());
LightReMutexHolder holder(*_states_lock);
// Is this composition already cached?
int index = _invert_composition_cache.find(other);
if (index != -1) {
Composition &comp = ((TransformState *)this)->_invert_composition_cache.modify_data(index);
if (comp._result == (const TransformState *)NULL) {
// Well, it wasn't cached already, but we already had an entry
// (probably created for the reverse direction), so use the same
// entry to store the new result.
comp._result = result;
if (result != (const TransformState *)this) {
// See the comments below about the need to up the reference
// count only when the result is not the same as this.
result->cache_ref();
}
}
// Here's the cache!
_cache_stats.inc_hits();
return comp._result;
}
_cache_stats.inc_misses();
// We need to make a new cache entry, both in this object and in the
// other object. We make both records so the other TransformState
// object will know to delete the entry from this object when it
// destructs, and vice-versa.
// The cache entry in this object is the only one that indicates the
// result; the other will be NULL for now.
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(_invert_composition_cache.get_size() == 0);
_invert_composition_cache[other]._result = result;
if (other != this) {
_cache_stats.add_total_size(1);
_cache_stats.inc_adds(other->_invert_composition_cache.get_size() == 0);
((TransformState *)other)->_invert_composition_cache[this]._result = NULL;
}
if (result != (TransformState *)this) {
// If the result of compose() is something other than this,
// explicitly increment the reference count. We have to be sure
// to decrement it again later, when the composition entry is
// removed from the cache.
result->cache_ref();
// (If the result was just this again, we still store the
// result, but we don't increment the reference count, since
// that would be a self-referential leak.)
}
return result;
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: TransformState::do_invert_compose // Function: TransformState::do_invert_compose
// Access: Private // Access: Private

View File

@ -234,7 +234,9 @@ private:
static CPT(TransformState) return_unique(TransformState *state); static CPT(TransformState) return_unique(TransformState *state);
CPT(TransformState) do_compose(const TransformState *other) const; CPT(TransformState) do_compose(const TransformState *other) const;
CPT(TransformState) store_compose(const TransformState *other, const TransformState *result);
CPT(TransformState) do_invert_compose(const TransformState *other) const; CPT(TransformState) do_invert_compose(const TransformState *other) const;
CPT(TransformState) store_invert_compose(const TransformState *other, const TransformState *result);
static bool r_detect_cycles(const TransformState *start_state, static bool r_detect_cycles(const TransformState *start_state,
const TransformState *current_state, const TransformState *current_state,
int length, UpdateSeq this_seq, int length, UpdateSeq this_seq,

View File

@ -213,16 +213,20 @@ load_gridded_models(WindowFramework *window,
// Load up all the files indicated in the list of gridded filenames // Load up all the files indicated in the list of gridded filenames
// and store them in the given vector. // and store them in the given vector.
Loader loader;
LoaderOptions options;
options.set_flags(options.get_flags() | LoaderOptions::LF_no_ram_cache);
// First, load up each model from disk once, and store them all // First, load up each model from disk once, and store them all
// separate from the scene graph. Also count up the total number of // separate from the scene graph. Also count up the total number of
// models we'll be putting in the grid. // models we'll be putting in the grid.
int grid_count = 0; int grid_count = 0;
NodePath models("models");
GriddedFilenames::iterator fi; GriddedFilenames::iterator fi;
for (fi = filenames.begin(); fi != filenames.end(); ++fi) { for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
GriddedFilename &gf = (*fi); GriddedFilename &gf = (*fi);
gf._model = window->load_model(models, gf._filename); PT(PandaNode) node = loader.load_sync(gf._filename, options);
if (!gf._model.is_empty()) { if (node != (PandaNode *)NULL) {
gf._model = NodePath(node);
grid_count += gf._count; grid_count += gf._count;
} }
} }
@ -254,7 +258,7 @@ load_gridded_models(WindowFramework *window,
int passnum = 0; int passnum = 0;
bool loaded_any; bool loaded_any;
NodePath render = window->get_render(); NodePath root = window->get_panda_framework()->get_models();
do { do {
loaded_any = false; loaded_any = false;
@ -265,9 +269,15 @@ load_gridded_models(WindowFramework *window,
// Copy this model into the scene graph, and assign it a // Copy this model into the scene graph, and assign it a
// position on the grid. // position on the grid.
string model_name = format_string(++model_count); ++model_count;
NodePath model = render.attach_new_node(model_name); PT(PandaNode) node = loader.load_sync(gf._filename, options);
gf._model.copy_to(model); NodePath model;
if (node == (PandaNode *)NULL) {
model = gf._model.copy_to(NodePath());
} else {
model = NodePath(node);
}
model.reparent_to(root);
gridded_file_info info; gridded_file_info info;
info.node = model.node(); info.node = model.node();
@ -369,14 +379,6 @@ load_gridded_models(WindowFramework *window,
passnum++; passnum++;
} while (loaded_any); } while (loaded_any);
// Finally, remove the source models we loaded up. Not a real big deal.
for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
GriddedFilename &gf = (*fi);
if (!gf._model.is_empty()) {
gf._model.remove_node();
}
}
} }
int int
@ -402,13 +404,15 @@ main(int argc, char *argv[]) {
window->enable_keyboard(); window->enable_keyboard();
window->setup_trackball(); window->setup_trackball();
window->load_models(window->get_render(), static_filenames); window->load_models(framework.get_models(), static_filenames);
framework.get_models().instance_to(window->get_render());
GriddedInfoArray info_arr; GriddedInfoArray info_arr;
load_gridded_models(window, gridded_filenames, info_arr); load_gridded_models(window, gridded_filenames, info_arr);
window->loop_animations(); window->loop_animations();
window->stagger_animations(); window->stagger_animations();
window->center_trackball(framework.get_models());
framework.enable_default_keys(); framework.enable_default_keys();