/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file transformState.cxx * @author drose * @date 2002-02-25 */ #include "transformState.h" #include "compose_matrix.h" #include "bamReader.h" #include "bamWriter.h" #include "datagramIterator.h" #include "indent.h" #include "compareTo.h" #include "pStatTimer.h" #include "config_pgraph.h" #include "lightReMutexHolder.h" #include "lightMutexHolder.h" #include "thread.h" using std::ostream; LightReMutex *TransformState::_states_lock = nullptr; TransformState::States TransformState::_states; CPT(TransformState) TransformState::_identity_state; CPT(TransformState) TransformState::_invalid_state; UpdateSeq TransformState::_last_cycle_detect; size_t TransformState::_garbage_index = 0; bool TransformState::_uniquify_matrix = true; PStatCollector TransformState::_cache_update_pcollector("*:State Cache:Update"); PStatCollector TransformState::_garbage_collect_pcollector("*:State Cache:Garbage Collect"); PStatCollector TransformState::_transform_compose_pcollector("*:State Cache:Compose Transform"); PStatCollector TransformState::_transform_invert_pcollector("*:State Cache:Invert Transform"); PStatCollector TransformState::_transform_calc_pcollector("*:State Cache:Calc Components"); PStatCollector TransformState::_transform_break_cycles_pcollector("*:State Cache:Break Cycles"); PStatCollector TransformState::_transform_new_pcollector("*:State Cache:New"); PStatCollector TransformState::_transform_validate_pcollector("*:State Cache:Validate"); PStatCollector TransformState::_transform_hash_pcollector("*:State Cache:Calc Hash"); PStatCollector TransformState::_node_counter("TransformStates:On nodes"); PStatCollector TransformState::_cache_counter("TransformStates:Cached"); CacheStats TransformState::_cache_stats; TypeHandle TransformState::_type_handle; /** * */ TransformState:: TransformState() : _flags(F_is_identity | F_singular_known | F_is_2d), _lock("TransformState") { if (_states_lock == nullptr) { init_states(); } _cache_stats.add_num_states(1); #ifdef DO_MEMORY_USAGE MemoryUsage::update_type(this, this); #endif } /** * The destructor is responsible for removing the TransformState from the * global set if it is there. */ TransformState:: ~TransformState() { // We'd better not call the destructor twice on a particular object. nassertv(!is_destructing()); set_destructing(); // Free the inverse matrix computation, if it has been stored. delete _inv_mat; _inv_mat = nullptr; LightReMutexHolder holder(*_states_lock); // unref() should have cleared these. nassertv(_saved_entry == -1); nassertv(_composition_cache.is_empty() && _invert_composition_cache.is_empty()); // If this was true at the beginning of the destructor, but is no longer // true now, probably we've been double-deleted. nassertv(get_ref_count() == 0); _cache_stats.add_num_states(-1); #ifndef NDEBUG _flags = F_is_invalid | F_is_destructing; #endif } /** * Provides an arbitrary ordering among all unique TransformStates, so we can * store the essentially different ones in a big set and throw away the rest. * * Note that if this returns 0, it doesn't necessarily imply that operator == * returns true; it uses a very slightly different comparison threshold. * * If uniquify_matrix is true, then matrix-defined TransformStates are also * uniqified. If uniquify_matrix is false, then only component-defined * TransformStates are uniquified, which is less expensive. */ int TransformState:: compare_to(const TransformState &other, bool uniquify_matrix) const { static const int significant_flags = (F_is_invalid | F_is_identity | F_components_given | F_hpr_given | F_quat_given | F_is_2d); int flags = (_flags & significant_flags); int other_flags = (other._flags & significant_flags); if (flags != other_flags) { return flags < other_flags ? -1 : 1; } if ((_flags & (F_is_invalid | F_is_identity)) != 0) { // All invalid transforms are equivalent to each other, and all identity // transforms are equivalent to each other. return 0; } if ((_flags & F_components_given) != 0) { // If the transform was specified componentwise, compare them // componentwise. int c = _pos.compare_to(other._pos); if (c != 0) { return c; } if ((_flags & F_hpr_given) != 0) { c = _hpr.compare_to(other._hpr); if (c != 0) { return c; } } else if ((_flags & F_quat_given) != 0) { c = _quat.compare_to(other._quat); if (c != 0) { return c; } } c = _scale.compare_to(other._scale); if (c != 0) { return c; } c = _shear.compare_to(other._shear); return c; } // Otherwise, compare the matrices . . . if (uniquify_matrix) { // . . . but only if the user thinks that's a worthwhile comparison. return get_mat().compare_to(other.get_mat()); } else { // If not, we just compare the pointers. if (this != &other) { return (this < &other) ? -1 : 1; } return 0; } } /** * Tests equivalence between two transform states. We use this instead of * compare_to since this is faster, and we don't need an ordering between * TransformStates because we use a hash map. * * If uniquify_matrix is true, then matrix-defined TransformStates are also * uniqified. If uniquify_matrix is false, then only component-defined * TransformStates are uniquified, which is less expensive. */ bool TransformState:: operator == (const TransformState &other) const { static const int significant_flags = (F_is_invalid | F_is_identity | F_components_given | F_hpr_given | F_quat_given | F_is_2d); int flags = (_flags & significant_flags); int other_flags = (other._flags & significant_flags); if (flags != other_flags) { return false; } if ((_flags & (F_is_invalid | F_is_identity)) != 0) { // All invalid transforms are equivalent to each other, and all identity // transforms are equivalent to each other. return true; } if ((_flags & F_components_given) != 0) { // If the transform was specified componentwise, compare them // componentwise. if (_pos != other._pos) { return false; } if ((_flags & F_hpr_given) != 0) { if (_hpr != other._hpr) { return false; } } else if ((_flags & F_quat_given) != 0) { if (_quat != other._quat) { return false; } } if (_scale != other._scale) { return false; } return (_shear == other._shear); } // Otherwise, compare the matrices . . . if (_uniquify_matrix) { // . . . but only if the user thinks that's a worthwhile comparison. return get_mat().almost_equal(other.get_mat()); } else { // If not, we just compare the pointers. return (this == &other); } } /** * Makes a new TransformState with the specified components. */ CPT(TransformState) TransformState:: make_pos_hpr_scale_shear(const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale, const LVecBase3 &shear) { nassertr(!(pos.is_nan() || hpr.is_nan() || scale.is_nan() || shear.is_nan()), make_invalid()); // Make a special-case check for the identity transform. if (pos == LVecBase3(0.0f, 0.0f, 0.0f) && hpr == LVecBase3(0.0f, 0.0f, 0.0f) && scale == LVecBase3(1.0f, 1.0f, 1.0f) && shear == LVecBase3(0.0f, 0.0f, 0.0f)) { return make_identity(); } TransformState *state = new TransformState; state->_pos = pos; state->_hpr = hpr; state->_scale = scale; state->_shear = shear; state->_flags = F_components_given | F_hpr_given | F_components_known | F_hpr_known | F_has_components; state->check_uniform_scale(); return return_new(state); } /** * Makes a new TransformState with the specified components. */ CPT(TransformState) TransformState:: make_pos_quat_scale_shear(const LVecBase3 &pos, const LQuaternion &quat, const LVecBase3 &scale, const LVecBase3 &shear) { nassertr(!(pos.is_nan() || quat.is_nan() || scale.is_nan() || shear.is_nan()), make_invalid()); // Make a special-case check for the identity transform. if (pos == LVecBase3(0.0f, 0.0f, 0.0f) && quat == LQuaternion::ident_quat() && scale == LVecBase3(1.0f, 1.0f, 1.0f) && shear == LVecBase3(0.0f, 0.0f, 0.0f)) { return make_identity(); } TransformState *state = new TransformState; state->_pos = pos; state->_quat = quat; state->_scale = scale; state->_shear = shear; state->_flags = F_components_given | F_quat_given | F_components_known | F_quat_known | F_has_components; state->check_uniform_scale(); return return_new(state); } /** * Makes a new TransformState with the specified transformation matrix. */ CPT(TransformState) TransformState:: make_mat(const LMatrix4 &mat) { nassertr(!mat.is_nan(), make_invalid()); // Make a special-case check for the identity matrix. if (mat.is_identity()) { return make_identity(); } TransformState *state = new TransformState; state->_mat = mat; state->_flags = F_mat_known; return return_new(state); } /** * Makes a new two-dimensional TransformState with the specified components. */ CPT(TransformState) TransformState:: make_pos_rotate_scale_shear2d(const LVecBase2 &pos, PN_stdfloat rotate, const LVecBase2 &scale, PN_stdfloat shear) { nassertr(!(pos.is_nan() || cnan(rotate) || scale.is_nan() || cnan(shear)), make_invalid()); // Make a special-case check for the identity transform. if (pos == LVecBase2(0.0f, 0.0f) && rotate == 0.0f && scale == LVecBase2(1.0f, 1.0f) && shear == 0.0f) { return make_identity(); } TransformState *state = new TransformState; state->_pos.set(pos[0], pos[1], 0.0f); switch (get_default_coordinate_system()) { default: case CS_zup_right: state->_hpr.set(rotate, 0.0f, 0.0f); break; case CS_zup_left: state->_hpr.set(-rotate, 0.0f, 0.0f); break; case CS_yup_right: state->_hpr.set(0.0f, 0.0f, -rotate); break; case CS_yup_left: state->_hpr.set(0.0, 0.0f, rotate); break; } state->_scale.set(scale[0], scale[1], 1.0f); state->_shear.set(shear, 0.0f, 0.0f); state->_flags = F_components_given | F_hpr_given | F_components_known | F_hpr_known | F_has_components | F_is_2d; state->check_uniform_scale2d(); return return_new(state); } /** * Makes a new two-dimensional TransformState with the specified 3x3 * transformation matrix. */ CPT(TransformState) TransformState:: make_mat3(const LMatrix3 &mat) { nassertr(!mat.is_nan(), make_invalid()); // Make a special-case check for the identity matrix. if (mat.is_identity()) { return make_identity(); } TransformState *state = new TransformState; state->_mat.set(mat(0, 0), mat(0, 1), 0.0f, mat(0, 2), mat(1, 0), mat(1, 1), 0.0f, mat(1, 2), 0.0f, 0.0f, 1.0f, 0.0f, mat(2, 0), mat(2, 1), 0.0f, mat(2, 2)); state->_flags = F_mat_known | F_is_2d; return return_new(state); } /** * Returns a new TransformState object that represents the original * TransformState with its pos component replaced with the indicated value. */ CPT(TransformState) TransformState:: set_pos(const LVecBase3 &pos) const { nassertr(!pos.is_nan(), this); nassertr(!is_invalid(), this); if (is_identity() || components_given()) { // If we started with a componentwise transform, we keep it that way. if (quat_given()) { return make_pos_quat_scale_shear(pos, get_quat(), get_scale(), get_shear()); } else { return make_pos_hpr_scale_shear(pos, get_hpr(), get_scale(), get_shear()); } } else { // Otherwise, we have a matrix transform, and we keep it that way. LMatrix4 mat = get_mat(); mat.set_row(3, pos); return make_mat(mat); } } /** * Returns a new TransformState object that represents the original * TransformState with its rotation component replaced with the indicated * value, if possible. */ CPT(TransformState) TransformState:: set_hpr(const LVecBase3 &hpr) const { nassertr(!hpr.is_nan(), this); nassertr(!is_invalid(), this); // nassertr(has_components(), this); return make_pos_hpr_scale_shear(get_pos(), hpr, get_scale(), get_shear()); } /** * Returns a new TransformState object that represents the original * TransformState with its rotation component replaced with the indicated * value, if possible. */ CPT(TransformState) TransformState:: set_quat(const LQuaternion &quat) const { nassertr(!quat.is_nan(), this); nassertr(!is_invalid(), this); // nassertr(has_components(), this); return make_pos_quat_scale_shear(get_pos(), quat, get_scale(), get_shear()); } /** * Returns a new TransformState object that represents the original * TransformState with its scale component replaced with the indicated value, * if possible. */ CPT(TransformState) TransformState:: set_scale(const LVecBase3 &scale) const { nassertr(!scale.is_nan(), this); nassertr(!is_invalid(), this); if (is_2d() && scale[0] == scale[1] && scale[1] == scale[2]) { // Don't inflate from 2-d to 3-d just because we got a uniform scale. return make_pos_rotate_scale_shear2d(get_pos2d(), get_rotate2d(), LVecBase2(scale[0], scale[0]), get_shear2d()); } // nassertr(has_components(), this); if (quat_given()) { return make_pos_quat_scale_shear(get_pos(), get_quat(), scale, get_shear()); } else { return make_pos_hpr_scale_shear(get_pos(), get_hpr(), scale, get_shear()); } } /** * Returns a new TransformState object that represents the original * TransformState with its shear component replaced with the indicated value, * if possible. */ CPT(TransformState) TransformState:: set_shear(const LVecBase3 &shear) const { nassertr(!shear.is_nan(), this); nassertr(!is_invalid(), this); // nassertr(has_components(), this); if (quat_given()) { return make_pos_quat_scale_shear(get_pos(), get_quat(), get_scale(), shear); } else { return make_pos_hpr_scale_shear(get_pos(), get_hpr(), get_scale(), shear); } } /** * Returns a new TransformState object that represents the original 2-d * TransformState with its pos component replaced with the indicated value. */ CPT(TransformState) TransformState:: set_pos2d(const LVecBase2 &pos) const { nassertr(!pos.is_nan(), this); nassertr(!is_invalid(), this); if (!is_2d()) { return set_pos(LVecBase3(pos[0], pos[1], 0.0f)); } if (is_identity() || components_given()) { // If we started with a componentwise transform, we keep it that way. return make_pos_rotate_scale_shear2d(pos, get_rotate2d(), get_scale2d(), get_shear2d()); } else { // Otherwise, we have a matrix transform, and we keep it that way. LMatrix3 mat = get_mat3(); mat.set_row(2, pos); return make_mat3(mat); } } /** * Returns a new TransformState object that represents the original 2-d * TransformState with its rotation component replaced with the indicated * value, if possible. */ CPT(TransformState) TransformState:: set_rotate2d(PN_stdfloat rotate) const { nassertr(!cnan(rotate), this); nassertr(!is_invalid(), this); if (!is_2d()) { switch (get_default_coordinate_system()) { default: case CS_zup_right: return set_hpr(LVecBase3(rotate, 0.0f, 0.0f)); case CS_zup_left: return set_hpr(LVecBase3(-rotate, 0.0f, 0.0f)); case CS_yup_right: return set_hpr(LVecBase3(0.0f, 0.0f, -rotate)); case CS_yup_left: return set_hpr(LVecBase3(0.0f, 0.0f, rotate)); } } return make_pos_rotate_scale_shear2d(get_pos2d(), rotate, get_scale2d(), get_shear2d()); } /** * Returns a new TransformState object that represents the original 2-d * TransformState with its scale component replaced with the indicated value, * if possible. */ CPT(TransformState) TransformState:: set_scale2d(const LVecBase2 &scale) const { nassertr(!scale.is_nan(), this); nassertr(!is_invalid(), this); if (!is_2d()) { return set_scale(LVecBase3(scale[0], scale[1], 1.0f)); } return make_pos_rotate_scale_shear2d(get_pos2d(), get_rotate2d(), scale, get_shear2d()); } /** * Returns a new TransformState object that represents the original 2-d * TransformState with its shear component replaced with the indicated value, * if possible. */ CPT(TransformState) TransformState:: set_shear2d(PN_stdfloat shear) const { nassertr(!cnan(shear), this); nassertr(!is_invalid(), this); if (!is_2d()) { return set_shear(LVecBase3(shear, 0.0f, 0.0f)); } return make_pos_rotate_scale_shear2d(get_pos2d(), get_rotate2d(), get_scale2d(), shear); } /** * Returns a new TransformState object that represents the composition of this * state with the other state. * * The result of this operation is cached, and will be retained as long as * both this TransformState object and the other TransformState object * continue to exist. Should one of them destruct, the cached entry will be * removed, and its pointer will be allowed to destruct as well. */ CPT(TransformState) TransformState:: compose(const TransformState *other) const { // We handle identity as a trivial special case. if (is_identity()) { return other; } if (other->is_identity()) { return this; } // If either transform is invalid, the result is invalid. if (is_invalid()) { return this; } if (other->is_invalid()) { return other; } if (!transform_cache) { return do_compose(other); } LightReMutexHolder holder(*_states_lock); // Is this composition already cached? int index = _composition_cache.find(other); if (index != -1) { const Composition &comp = _composition_cache.get_data(index); if (comp._result != nullptr) { // Success! _cache_stats.inc_hits(); return comp._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. CPT(TransformState) result = do_compose(other); if (index != -1) { Composition &comp = _composition_cache.modify_data(index); // 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 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.is_empty()); _composition_cache[other]._result = result; if (other != this) { _cache_stats.add_total_size(1); _cache_stats.inc_adds(other->_composition_cache.is_empty()); other->_composition_cache[this]._result = nullptr; } 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; } /** * Returns a new TransformState object that represents the composition of this * state's inverse with the other state. * * This is similar to compose(), but is particularly useful for computing the * relative state of a node as viewed from some other node. */ CPT(TransformState) TransformState:: invert_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. if (is_identity()) { return other; } // Unlike compose(), the case of other->is_identity() is not quite as // trivial for invert_compose(). // If either transform is invalid, the result is invalid. if (is_invalid()) { return this; } if (other->is_invalid()) { return other; } if (other == this) { // a->invert_compose(a) always produces identity. return _identity_state; } if (!transform_cache) { return do_invert_compose(other); } LightReMutexHolder holder(*_states_lock); int index = _invert_composition_cache.find(other); if (index != -1) { const Composition &comp = _invert_composition_cache.get_data(index); if (comp._result != nullptr) { // Success! _cache_stats.inc_hits(); return comp._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. CPT(TransformState) result = do_invert_compose(other); // Is this composition already cached? if (index != -1) { Composition &comp = _invert_composition_cache.modify_data(index); // 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 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.is_empty()); _invert_composition_cache[other]._result = result; if (other != this) { _cache_stats.add_total_size(1); _cache_stats.inc_adds(other->_invert_composition_cache.is_empty()); other->_invert_composition_cache[this]._result = nullptr; } 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; } /** * This method overrides ReferenceCount::unref() to check whether the * remaining reference count is entirely in the cache, and if so, it checks * for and breaks a cycle in the cache involving this object. This is * designed to prevent leaks from cyclical references within the cache. */ bool TransformState:: unref() const { if (garbage_collect_states || !transform_cache) { // If we're not using the cache at all, or if we're relying on garbage // collection, just allow the pointer to unref normally. return ReferenceCount::unref(); } // Here is the normal refcounting case, with a normal cache, and without // garbage collection in effect. In this case we will pull the object out // of the cache when its reference count goes to 0. // 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. Having to grab // the lock at every call to unref() is a big limiting factor on // parallelization. LightReMutexHolder holder(*_states_lock); if (auto_break_cycles && uniquify_transforms) { if (get_cache_ref_count() > 0 && get_ref_count() == get_cache_ref_count() + 1) { // If we are about to remove the one reference that is not in the cache, // leaving only references in the cache, then we need to check for a // cycle involving this TransformState and break it if it exists. ((TransformState *)this)->detect_and_break_cycles(); } } if (ReferenceCount::unref()) { // The reference count is still nonzero. return true; } // The reference count has just reached zero. Make sure the object is // removed from the global object pool, before anyone else finds it and // tries to ref it. ((TransformState *)this)->release_new(); ((TransformState *)this)->remove_cache_pointers(); return false; } /** * Returns true if the composition cache and invert composition cache for this * particular TransformState are self-consistent and valid, false otherwise. */ bool TransformState:: validate_composition_cache() const { LightReMutexHolder holder(*_states_lock); size_t size = _composition_cache.get_num_entries(); for (size_t i = 0; i < size; ++i) { const TransformState *source = _composition_cache.get_key(i); if (source != nullptr) { // Check that the source also has a pointer back to this one. We always // add entries to the composition cache in pairs. int ri = source->_composition_cache.find(this); if (ri == -1) { // Failure! There is no back-pointer. pgraph_cat.error() << "TransformState::composition cache is inconsistent!\n"; pgraph_cat.error(false) << *this << " compose " << *source << "\n"; pgraph_cat.error(false) << "but no reverse\n"; return false; } } } size = _invert_composition_cache.get_num_entries(); for (size_t i = 0; i < size; ++i) { const TransformState *source = _invert_composition_cache.get_key(i); if (source != nullptr) { // Check that the source also has a pointer back to this one. We always // add entries to the composition cache in pairs. int ri = source->_invert_composition_cache.find(this); if (ri == -1) { // Failure! There is no back-pointer. pgraph_cat.error() << "TransformState::invert composition cache is inconsistent!\n"; pgraph_cat.error(false) << *this << " invert compose " << *source << "\n"; pgraph_cat.error(false) << "but no reverse\n"; return false; } } } return true; } /** * */ void TransformState:: output(ostream &out) const { out << "T:"; if (is_invalid()) { out << "(invalid)"; } else if (is_identity()) { out << "(identity)"; } else if (has_components()) { bool output_hpr = !get_hpr().almost_equal(LVecBase3(0.0f, 0.0f, 0.0f)); if (!components_given()) { // A leading "m" indicates the transform was described as a full matrix, // and we are decomposing it for the benefit of the user. out << "m"; } else if (output_hpr && quat_given()) { // A leading "q" indicates that the pos, scale, and shear are exactly as // specified, but the rotation was described as a quaternion, and we are // decomposing that to hpr for the benefit of the user. out << "q"; } char lead = '('; if (is_2d()) { if (!get_pos2d().almost_equal(LVecBase2(0.0f, 0.0f))) { out << lead << "pos " << get_pos2d(); lead = ' '; } if (output_hpr) { out << lead << "rotate " << get_rotate2d(); lead = ' '; } if (!get_scale2d().almost_equal(LVecBase2(1.0f, 1.0f))) { if (has_uniform_scale()) { out << lead << "scale " << get_uniform_scale(); lead = ' '; } else { out << lead << "scale " << get_scale2d(); lead = ' '; } } if (has_nonzero_shear()) { out << lead << "shear " << get_shear2d(); lead = ' '; } } else { if (!get_pos().almost_equal(LVecBase3(0.0f, 0.0f, 0.0f))) { out << lead << "pos " << get_pos(); lead = ' '; } if (output_hpr) { out << lead << "hpr " << get_hpr(); lead = ' '; } if (!get_scale().almost_equal(LVecBase3(1.0f, 1.0f, 1.0f))) { if (has_uniform_scale()) { out << lead << "scale " << get_uniform_scale(); lead = ' '; } else { out << lead << "scale " << get_scale(); lead = ' '; } } if (has_nonzero_shear()) { out << lead << "shear " << get_shear(); lead = ' '; } } if (lead == '(') { out << "(almost identity)"; } else { out << ")"; } } else { if (is_2d()) { out << get_mat3(); } else { out << get_mat(); } } } /** * */ void TransformState:: write(ostream &out, int indent_level) const { indent(out, indent_level) << *this << "\n"; } /** * Writes a brief description of the composition cache and invert composition * cache to the indicated ostream. This is not useful except for performance * analysis, to examine the cache structure. */ void TransformState:: write_composition_cache(ostream &out, int indent_level) const { indent(out, indent_level + 2) << _composition_cache << "\n"; indent(out, indent_level + 2) << _invert_composition_cache << "\n"; } /** * Returns the total number of unique TransformState objects allocated in the * world. This will go up and down during normal operations. */ int TransformState:: get_num_states() { LightReMutexHolder holder(*_states_lock); return _states.get_num_entries(); } /** * Returns the total number of TransformState objects that have been allocated * but have no references outside of the internal TransformState cache. * * A nonzero return value is not necessarily indicative of leaked references; * it is normal for two TransformState objects, both of which have references * held outside the cache, to have the result of their composition stored * within the cache. This result will be retained within the cache until one * of the base TransformStates is released. * * Use list_cycles() to get an idea of the number of actual "leaked" * TransformState objects. */ int TransformState:: get_num_unused_states() { LightReMutexHolder holder(*_states_lock); // First, we need to count the number of times each TransformState object is // recorded in the cache. We could just trust get_cache_ref_count(), but // we'll be extra cautious for now. typedef pmap StateCount; StateCount state_count; size_t size = _states.get_num_entries(); for (size_t si = 0; si < size; ++si) { const TransformState *state = _states.get_key(si); std::pair ir = state_count.insert(StateCount::value_type(state, 1)); if (!ir.second) { // If the above insert operation fails, then it's already in the // cache; increment its value. (*(ir.first)).second++; } size_t i; size_t cache_size = state->_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *result = state->_composition_cache.get_data(i)._result; if (result != nullptr && result != state) { // Here's a TransformState that's recorded in the cache. Count it. std::pair ir = state_count.insert(StateCount::value_type(result, 1)); if (!ir.second) { // If the above insert operation fails, then it's already in the // cache; increment its value. (*(ir.first)).second++; } } } cache_size = state->_invert_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *result = state->_invert_composition_cache.get_data(i)._result; if (result != nullptr && result != state) { std::pair ir = state_count.insert(StateCount::value_type(result, 1)); if (!ir.second) { (*(ir.first)).second++; } } } } // Now that we have the appearance count of each TransformState object, we // can tell which ones are unreferenced outside of the TransformState cache, // by comparing these to the reference counts. int num_unused = 0; StateCount::iterator sci; for (sci = state_count.begin(); sci != state_count.end(); ++sci) { const TransformState *state = (*sci).first; int count = (*sci).second; nassertr(count == state->get_cache_ref_count(), num_unused); nassertr(count <= state->get_ref_count(), num_unused); if (count == state->get_ref_count()) { num_unused++; if (pgraph_cat.is_debug()) { pgraph_cat.debug() << "Unused state: " << (void *)state << ":" << state->get_ref_count() << " =\n"; state->write(pgraph_cat.debug(false), 2); } } } return num_unused; } /** * Empties the cache of composed TransformStates. This makes every * TransformState forget what results when it is composed with other * TransformStates. * * This will eliminate any TransformState objects that have been allocated but * have no references outside of the internal TransformState map. It will not * eliminate TransformState objects that are still in use. * * Nowadays, this method should not be necessary, as reference-count cycles in * the composition cache should be automatically detected and broken. * * The return value is the number of TransformStates freed by this operation. */ int TransformState:: clear_cache() { LightReMutexHolder holder(*_states_lock); PStatTimer timer(_cache_update_pcollector); int orig_size = _states.get_num_entries(); // First, we need to copy the entire set of states to a temporary vector, // reference-counting each object. That way we can walk through the copy, // without fear of dereferencing (and deleting) the objects in the map as we // go. { typedef pvector< CPT(TransformState) > TempStates; TempStates temp_states; temp_states.reserve(orig_size); size_t size = _states.get_num_entries(); for (size_t si = 0; si < size; ++si) { const TransformState *state = _states.get_key(si); temp_states.push_back(state); } // Now it's safe to walk through the list, destroying the cache within // each object as we go. Nothing will be destructed till we're done. TempStates::iterator ti; for (ti = temp_states.begin(); ti != temp_states.end(); ++ti) { TransformState *state = (TransformState *)(*ti).p(); size_t i; size_t cache_size = state->_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *result = state->_composition_cache.get_data(i)._result; if (result != nullptr && result != state) { result->cache_unref(); nassertr(result->get_ref_count() > 0, 0); } } _cache_stats.add_total_size(-(int)state->_composition_cache.get_num_entries()); state->_composition_cache.clear(); cache_size = state->_invert_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *result = state->_invert_composition_cache.get_data(i)._result; if (result != nullptr && result != state) { result->cache_unref(); nassertr(result->get_ref_count() > 0, 0); } } _cache_stats.add_total_size(-(int)state->_invert_composition_cache.get_num_entries()); state->_invert_composition_cache.clear(); } // Once this block closes and the temp_states object goes away, all the // destruction will begin. Anything whose reference was held only within // the various objects' caches will go away. } int new_size = _states.get_num_entries(); return orig_size - new_size; } /** * Performs a garbage-collection cycle. This must be called periodically if * garbage-collect-states is true to ensure that TransformStates get cleaned * up appropriately. It does no harm to call it even if this variable is not * true, but there is probably no advantage in that case. */ int TransformState:: garbage_collect() { if (!garbage_collect_states) { return 0; } LightReMutexHolder holder(*_states_lock); PStatTimer timer(_garbage_collect_pcollector); size_t orig_size = _states.get_num_entries(); // How many elements to process this pass? size_t size = orig_size; size_t num_this_pass = std::max(0, int(size * garbage_collect_states_rate)); if (num_this_pass <= 0) { return 0; } bool break_and_uniquify = (auto_break_cycles && uniquify_transforms); size_t si = _garbage_index; if (si >= size) { si = 0; } num_this_pass = std::min(num_this_pass, size); size_t stop_at_element = (si + num_this_pass) % size; do { TransformState *state = (TransformState *)_states.get_key(si); if (break_and_uniquify) { if (state->get_cache_ref_count() > 0 && state->get_ref_count() == state->get_cache_ref_count()) { // If we have removed all the references to this state not in the // cache, leaving only references in the cache, then we need to // check for a cycle involving this TransformState and break it if // it exists. state->detect_and_break_cycles(); } } if (!state->unref_if_one()) { // This state has recently been unreffed to 1 (the one we added when // we stored it in the cache). Now it's time to delete it. This is // safe, because we're holding the _states_lock, so it's not possible // for some other thread to find the state in the cache and ref it // while we're doing this. Also, we've just made sure to unref it to 0, // to ensure that another thread can't get it via a weak pointer. state->release_new(); state->remove_cache_pointers(); state->cache_unref_only(); delete state; // When we removed it from the hash map, it swapped the last element // with the one we just removed. So the current index contains one we // still need to visit. --size; --si; if (stop_at_element > 0) { --stop_at_element; } } si = (si + 1) % size; } while (si != stop_at_element); _garbage_index = si; nassertr(_states.get_num_entries() == size, 0); #ifdef _DEBUG nassertr(_states.validate(), 0); #endif // If we just cleaned up a lot of states, see if we can reduce the table in // size. This will help reduce iteration overhead in the future. _states.consider_shrink_table(); return (int)orig_size - (int)size; } /** * Detects all of the reference-count cycles in the cache and reports them to * standard output. * * These cycles may be inadvertently created when state compositions cycle * back to a starting point. Nowadays, these cycles should be automatically * detected and broken, so this method should never list any cycles unless * there is a bug in that detection logic. * * The cycles listed here are not leaks in the strictest sense of the word, * since they can be reclaimed by a call to clear_cache(); but they will not * be reclaimed automatically. */ void TransformState:: list_cycles(ostream &out) { LightReMutexHolder holder(*_states_lock); typedef pset VisitedStates; VisitedStates visited; CompositionCycleDesc cycle_desc; size_t size = _states.get_num_entries(); for (size_t si = 0; si < size; ++si) { const TransformState *state = _states.get_key(si); bool inserted = visited.insert(state).second; if (inserted) { ++_last_cycle_detect; if (r_detect_cycles(state, state, 1, _last_cycle_detect, &cycle_desc)) { // This state begins a cycle. CompositionCycleDesc::reverse_iterator csi; out << "\nCycle detected of length " << cycle_desc.size() + 1 << ":\n" << "state " << (void *)state << ":" << state->get_ref_count() << " =\n"; state->write(out, 2); for (csi = cycle_desc.rbegin(); csi != cycle_desc.rend(); ++csi) { const CompositionCycleDescEntry &entry = (*csi); if (entry._inverted) { out << "invert composed with "; } else { out << "composed with "; } out << (const void *)entry._obj << ":" << entry._obj->get_ref_count() << " " << *entry._obj << "\n" << "produces " << (const void *)entry._result << ":" << entry._result->get_ref_count() << " =\n"; entry._result->write(out, 2); visited.insert(entry._result); } cycle_desc.clear(); } else { ++_last_cycle_detect; if (r_detect_reverse_cycles(state, state, 1, _last_cycle_detect, &cycle_desc)) { // This state begins a cycle. CompositionCycleDesc::iterator csi; out << "\nReverse cycle detected of length " << cycle_desc.size() + 1 << ":\n" << "state "; for (csi = cycle_desc.begin(); csi != cycle_desc.end(); ++csi) { const CompositionCycleDescEntry &entry = (*csi); out << (const void *)entry._result << ":" << entry._result->get_ref_count() << " =\n"; entry._result->write(out, 2); out << (const void *)entry._obj << ":" << entry._obj->get_ref_count() << " =\n"; entry._obj->write(out, 2); visited.insert(entry._result); } out << (void *)state << ":" << state->get_ref_count() << " =\n"; state->write(out, 2); cycle_desc.clear(); } } } } } /** * Lists all of the TransformStates in the cache to the output stream, one per * line. This can be quite a lot of output if the cache is large, so be * prepared. */ void TransformState:: list_states(ostream &out) { LightReMutexHolder holder(*_states_lock); size_t size = _states.get_num_entries(); out << size << " states:\n"; for (size_t si = 0; si < size; ++si) { const TransformState *state = _states.get_key(si); state->write(out, 2); } } /** * Ensures that the cache is still stored in sorted order, and that none of * the cache elements have been inadvertently deleted. Returns true if so, * false if there is a problem (which implies someone has modified one of the * supposedly-const TransformState objects). */ bool TransformState:: validate_states() { PStatTimer timer(_transform_validate_pcollector); LightReMutexHolder holder(*_states_lock); if (_states.is_empty()) { return true; } if (!_states.validate()) { pgraph_cat.error() << "TransformState::_states cache is invalid!\n"; return false; } size_t size = _states.get_num_entries(); size_t si = 0; nassertr(si < size, false); nassertr(_states.get_key(si)->get_ref_count() >= 0, false); size_t snext = si; ++snext; while (snext < size) { nassertr(_states.get_key(snext)->get_ref_count() >= 0, false); const TransformState *ssi = _states.get_key(si); if (!ssi->validate_composition_cache()) { return false; } const TransformState *ssnext = _states.get_key(snext); bool c = (*ssi) == (*ssnext); bool ci = (*ssnext) == (*ssi); if (c != ci) { pgraph_cat.error() << "TransformState::operator == () not defined properly!\n"; pgraph_cat.error(false) << "(a, b): " << c << "\n"; pgraph_cat.error(false) << "(b, a): " << ci << "\n"; ssi->write(pgraph_cat.error(false), 2); ssnext->write(pgraph_cat.error(false), 2); return false; } si = snext; ++snext; } return true; } /** * Make sure the global _states map is allocated. This only has to be done * once. We could make this map static, but then we run into problems if * anyone creates a TransformState object at static init time; it also seems * to cause problems when the Panda shared library is unloaded at application * exit time. */ void TransformState:: init_states() { ConfigVariableBool uniquify_matrix ("uniquify-matrix", true, PRC_DESC("Set this true to look up arbitrary 4x4 transform matrices in " "the cache, to ensure that two differently-computed transforms " "that happen to encode the same matrix will be collapsed into " "a single pointer. Nowadays, with the transforms stored in a " "hashtable, we're generally better off with this set true.")); // Store this at the beginning, so that we don't have to query this every // time that the comparison operator is invoked. _uniquify_matrix = uniquify_matrix; // TODO: we should have a global Panda mutex to allow us to safely create // _states_lock without a startup race condition. For the meantime, this is // OK because we guarantee that this method is called at static init time, // presumably when there is still only one thread in the world. _states_lock = new LightReMutex("TransformState::_states_lock"); _cache_stats.init(); nassertv(Thread::get_current_thread() == Thread::get_main_thread()); // The identity and invalid states are asked for so often, we make them a // special case and store a pointer forever. { TransformState *state = new TransformState; state->_pos.set(0.0f, 0.0f, 0.0f); state->_scale.set(1.0f, 1.0f, 1.0f); state->_shear.set(0.0f, 0.0f, 0.0f); state->_hpr.set(0.0f, 0.0f, 0.0f); state->_quat.set(1.0f, 0.0f, 0.0f, 0.0f); state->_norm_quat.set(1.0f, 0.0f, 0.0f, 0.0f); state->_mat.set(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); state->_inv_mat = new LMatrix4(state->_mat); state->_hash = H_identity; state->_flags = F_is_identity | F_singular_known | F_components_known | F_has_components | F_mat_known | F_quat_known | F_hpr_known | F_uniform_scale | F_identity_scale | F_is_2d | F_norm_quat_known; state->cache_ref(); state->_saved_entry = _states.store(state, nullptr); _identity_state = state; } { TransformState *state = new TransformState; state->_hash = H_invalid; state->_flags = F_is_singular | F_singular_known | F_components_known | F_mat_known | F_is_invalid; state->cache_ref(); state->_saved_entry = _states.store(state, nullptr); _invalid_state = state; } } /** * This function is used to share a common TransformState pointer for all * equivalent TransformState objects. * * This is different from return_unique() in that it does not actually * guarantee a unique pointer, unless uniquify-transforms is set. */ CPT(TransformState) TransformState:: return_new(TransformState *state) { nassertr(state != nullptr, state); if (!uniquify_transforms && !state->is_identity()) { return state; } return return_unique(state); } /** * This function is used to share a common TransformState pointer for all * equivalent TransformState objects. * * See the similar logic in RenderState. The idea is to create a new * TransformState object and pass it through this function, which will share * the pointer with a previously-created TransformState object if it is * equivalent. */ CPT(TransformState) TransformState:: return_unique(TransformState *state) { nassertr(state != nullptr, state); if (!transform_cache) { return state; } #ifndef NDEBUG if (paranoid_const) { nassertr(validate_states(), state); } #endif PStatTimer timer(_transform_new_pcollector); LightReMutexHolder holder(*_states_lock); if (state->_saved_entry != -1) { // This state is already in the cache. nassertr(_states.find(state) == // state->_saved_entry, state); return state; } // Save the state in a local PointerTo so that it will be freed at the end // of this function if no one else uses it. CPT(TransformState) pt_state = state; int si = _states.find(state); if (si != -1) { // There's an equivalent state already in the set. Return it. return _states.get_key(si); } // Not already in the set; add it. if (garbage_collect_states) { // If we'll be garbage collecting states explicitly, we'll increment the // reference count when we store it in the cache, so that it won't be // deleted while it's in it. state->cache_ref(); } si = _states.store(state, nullptr); // Save the index and return the input state. state->_saved_entry = si; return pt_state; } /** * The private implemention of compose(); this actually composes two * TransformStates, without bothering with the cache. */ CPT(TransformState) TransformState:: do_compose(const TransformState *other) const { PStatTimer timer(_transform_compose_pcollector); nassertr((_flags & F_is_invalid) == 0, this); nassertr((other->_flags & F_is_invalid) == 0, other); if (compose_componentwise && has_uniform_scale() && !has_nonzero_shear() && !other->has_nonzero_shear() && ((components_given() && other->has_components()) || (other->components_given() && has_components()))) { // We will do this operation componentwise if *either* transform was given // componentwise (and there is no non-uniform scale in the way). CPT(TransformState) result; if (is_2d() && other->is_2d()) { // Do a 2-d compose. LVecBase2 pos = get_pos2d(); PN_stdfloat rotate = get_rotate2d(); LQuaternion quat = get_norm_quat(); PN_stdfloat scale = get_uniform_scale(); LPoint3 op = quat.xform(other->get_pos()); pos += LVecBase2(op[0], op[1]) * scale; rotate += other->get_rotate2d(); LVecBase2 new_scale = other->get_scale2d() * scale; result = make_pos_rotate_scale2d(pos, rotate, new_scale); } else { // A normal 3-d compose. LVecBase3 pos = get_pos(); LQuaternion quat = get_norm_quat(); PN_stdfloat scale = get_uniform_scale(); pos += quat.xform(other->get_pos()) * scale; quat = other->get_norm_quat() * quat; LVecBase3 new_scale = other->get_scale() * scale; result = make_pos_quat_scale(pos, quat, new_scale); } #ifndef NDEBUG if (paranoid_compose) { // Now verify against the matrix. LMatrix4 new_mat; new_mat.multiply(other->get_mat(), get_mat()); if (!new_mat.almost_equal(result->get_mat(), 0.1)) { CPT(TransformState) correct = make_mat(new_mat); pgraph_cat.warning() << "Componentwise composition of " << *this << " and " << *other << " produced:\n" << *result << "\n instead of:\n" << *correct << "\n"; result = correct; } } #endif // NDEBUG return result; } // Do the operation with matrices. if (is_2d() && other->is_2d()) { LMatrix3 new_mat = other->get_mat3() * get_mat3(); return make_mat3(new_mat); } else { LMatrix4 new_mat; new_mat.multiply(other->get_mat(), get_mat()); return make_mat(new_mat); } } /** * The private implemention of invert_compose(). */ CPT(TransformState) TransformState:: do_invert_compose(const TransformState *other) const { PStatTimer timer(_transform_invert_pcollector); nassertr((_flags & F_is_invalid) == 0, this); nassertr((other->_flags & F_is_invalid) == 0, other); if (compose_componentwise && has_uniform_scale() && !has_nonzero_shear() && !other->has_nonzero_shear() && ((components_given() && other->has_components()) || (other->components_given() && has_components()))) { // We will do this operation componentwise if *either* transform was given // componentwise (and there is no non-uniform scale in the way). CPT(TransformState) result; if (is_2d() && other->is_2d()) { // Do a 2-d invert compose. LVecBase2 pos = get_pos2d(); PN_stdfloat rotate = get_rotate2d(); LQuaternion quat = get_norm_quat(); PN_stdfloat scale = get_uniform_scale(); // First, invert our own transform. if (scale == 0.0f) { ((TransformState *)this)->_flags |= F_is_singular | F_singular_known; return _invalid_state; } scale = 1.0f / scale; quat.invert_in_place(); rotate = -rotate; LVecBase3 mp = quat.xform(-LVecBase3(pos[0], pos[1], 0.0f)); pos = LVecBase2(mp[0], mp[1]) * scale; LVecBase2 new_scale(scale, scale); // Now compose the inverted transform with the other transform. if (!other->is_identity()) { LPoint3 op = quat.xform(other->get_pos()); pos += LVecBase2(op[0], op[1]) * scale; rotate += other->get_rotate2d(); new_scale = other->get_scale2d() * scale; } result = make_pos_rotate_scale2d(pos, rotate, new_scale); } else { // Do a normal, 3-d invert compose. LVecBase3 pos = get_pos(); LQuaternion quat = get_norm_quat(); PN_stdfloat scale = get_uniform_scale(); // First, invert our own transform. if (scale == 0.0f) { ((TransformState *)this)->_flags |= F_is_singular | F_singular_known; return _invalid_state; } scale = 1.0f / scale; quat.invert_in_place(); pos = quat.xform(-pos) * scale; LVecBase3 new_scale(scale, scale, scale); // Now compose the inverted transform with the other transform. if (!other->is_identity()) { pos += quat.xform(other->get_pos()) * scale; quat = other->get_norm_quat() * quat; new_scale = other->get_scale() * scale; } result = make_pos_quat_scale(pos, quat, new_scale); } #ifndef NDEBUG if (paranoid_compose) { // Now verify against the matrix. if (is_singular()) { pgraph_cat.warning() << "Unexpected singular matrix found for " << *this << "\n"; } else { nassertr(_inv_mat != nullptr, _invalid_state); LMatrix4 new_mat; new_mat.multiply(other->get_mat(), *_inv_mat); if (!new_mat.almost_equal(result->get_mat(), 0.1)) { CPT(TransformState) correct = make_mat(new_mat); pgraph_cat.warning() << "Componentwise invert-composition of " << *this << " and " << *other << " produced:\n" << *result << "\n instead of:\n" << *correct << "\n"; result = correct; } } } #endif // NDEBUG return result; } if (is_singular()) { return _invalid_state; } // Now that is_singular() has returned false, we can assume that _inv_mat // has been allocated and filled in. nassertr(_inv_mat != nullptr, _invalid_state); if (is_2d() && other->is_2d()) { const LMatrix4 &i = *_inv_mat; LMatrix3 inv3(i(0, 0), i(0, 1), i(0, 3), i(1, 0), i(1, 1), i(1, 3), i(3, 0), i(3, 1), i(3, 3)); if (other->is_identity()) { return make_mat3(inv3); } else { return make_mat3(other->get_mat3() * inv3); } } else { if (other->is_identity()) { return make_mat(*_inv_mat); } else { return make_mat(other->get_mat() * (*_inv_mat)); } } } /** * Detects whether there is a cycle in the cache that begins with this state. * If any are detected, breaks them by removing this state from the cache. */ void TransformState:: detect_and_break_cycles() { PStatTimer timer(_transform_break_cycles_pcollector); ++_last_cycle_detect; if (r_detect_cycles(this, this, 1, _last_cycle_detect, nullptr)) { // Ok, we have a cycle. This will be a leak unless we break the cycle by // freeing the cache on this object. if (pgraph_cat.is_debug()) { pgraph_cat.debug() << "Breaking cycle involving " << (*this) << "\n"; } remove_cache_pointers(); } else { ++_last_cycle_detect; if (r_detect_reverse_cycles(this, this, 1, _last_cycle_detect, nullptr)) { if (pgraph_cat.is_debug()) { pgraph_cat.debug() << "Breaking cycle involving " << (*this) << "\n"; } remove_cache_pointers(); } } } /** * Detects whether there is a cycle in the cache that begins with the * indicated state. Returns true if at least one cycle is found, false if * this state is not part of any cycles. If a cycle is found and cycle_desc * is not NULL, then cycle_desc is filled in with the list of the steps of the * cycle, in reverse order. */ bool TransformState:: r_detect_cycles(const TransformState *start_state, const TransformState *current_state, int length, UpdateSeq this_seq, TransformState::CompositionCycleDesc *cycle_desc) { if (current_state->_cycle_detect == this_seq) { // We've already seen this state; therefore, we've found a cycle. // However, we only care about cycles that return to the starting state // and involve more than two steps. If only one or two nodes are // involved, it doesn't represent a memory leak, so no problem there. return (current_state == start_state && length > 2); } ((TransformState *)current_state)->_cycle_detect = this_seq; size_t i; size_t cache_size = current_state->_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *result = current_state->_composition_cache.get_data(i)._result; if (result != nullptr) { if (r_detect_cycles(start_state, result, length + 1, this_seq, cycle_desc)) { // Cycle detected. if (cycle_desc != nullptr) { const TransformState *other = current_state->_composition_cache.get_key(i); CompositionCycleDescEntry entry(other, result, false); cycle_desc->push_back(entry); } return true; } } } cache_size = current_state->_invert_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *result = current_state->_invert_composition_cache.get_data(i)._result; if (result != nullptr) { if (r_detect_cycles(start_state, result, length + 1, this_seq, cycle_desc)) { // Cycle detected. if (cycle_desc != nullptr) { const TransformState *other = current_state->_invert_composition_cache.get_key(i); CompositionCycleDescEntry entry(other, result, true); cycle_desc->push_back(entry); } return true; } } } // No cycle detected. return false; } /** * Works the same as r_detect_cycles, but checks for cycles in the reverse * direction along the cache chain. (A cycle may appear in either direction, * and we must check both.) */ bool TransformState:: r_detect_reverse_cycles(const TransformState *start_state, const TransformState *current_state, int length, UpdateSeq this_seq, TransformState::CompositionCycleDesc *cycle_desc) { if (current_state->_cycle_detect == this_seq) { // We've already seen this state; therefore, we've found a cycle. // However, we only care about cycles that return to the starting state // and involve more than two steps. If only one or two nodes are // involved, it doesn't represent a memory leak, so no problem there. return (current_state == start_state && length > 2); } ((TransformState *)current_state)->_cycle_detect = this_seq; size_t i; size_t cache_size = current_state->_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *other = current_state->_composition_cache.get_key(i); if (other != current_state) { int oi = other->_composition_cache.find(current_state); nassertr(oi != -1, false); const TransformState *result = other->_composition_cache.get_data(oi)._result; if (result != nullptr) { if (r_detect_reverse_cycles(start_state, result, length + 1, this_seq, cycle_desc)) { // Cycle detected. if (cycle_desc != nullptr) { const TransformState *other = current_state->_composition_cache.get_key(i); CompositionCycleDescEntry entry(other, result, false); cycle_desc->push_back(entry); } return true; } } } } cache_size = current_state->_invert_composition_cache.get_num_entries(); for (i = 0; i < cache_size; ++i) { const TransformState *other = current_state->_invert_composition_cache.get_key(i); if (other != current_state) { int oi = other->_invert_composition_cache.find(current_state); nassertr(oi != -1, false); const TransformState *result = other->_invert_composition_cache.get_data(oi)._result; if (result != nullptr) { if (r_detect_reverse_cycles(start_state, result, length + 1, this_seq, cycle_desc)) { // Cycle detected. if (cycle_desc != nullptr) { const TransformState *other = current_state->_invert_composition_cache.get_key(i); CompositionCycleDescEntry entry(other, result, false); cycle_desc->push_back(entry); } return true; } } } } // No cycle detected. return false; } /** * This inverse of return_new, this releases this object from the global * TransformState table. * * You must already be holding _states_lock before you call this method. */ void TransformState:: release_new() { nassertv(_states_lock->debug_is_locked()); if (_saved_entry != -1) { _saved_entry = -1; nassertv_always(_states.remove(this)); } } /** * Remove all pointers within the cache from and to this particular * TransformState. The pointers to this object may be scattered around in the * various CompositionCaches from other TransformState objects. * * You must already be holding _states_lock before you call this method. */ void TransformState:: remove_cache_pointers() { nassertv(_states_lock->debug_is_locked()); // Fortunately, since we added CompositionCache records in pairs, we know // exactly the set of TransformState objects that have us in their cache: // it's the same set of TransformState objects that we have in our own // cache. /* * We do need to put considerable thought into this loop, because as we clear * out cache entries we'll cause other TransformState objects to destruct, * which could cause things to get pulled out of our own _composition_cache * map. We want to allow this (so that we don't encounter any just-destructed * pointers in our cache), but we don't want to get bitten by this cascading * effect. Instead of walking through the map from beginning to end, * therefore, we just pull out the first one each time, and erase it. */ #ifdef DO_PSTATS if (_composition_cache.is_empty() && _invert_composition_cache.is_empty()) { return; } PStatTimer timer(_cache_update_pcollector); #endif // DO_PSTATS // There are lots of ways to do this loop wrong. Be very careful if you // need to modify it for any reason. size_t i = 0; while (!_composition_cache.is_empty()) { // It is possible that the "other" TransformState object is currently // within its own destructor. We therefore can't use a PT() to hold its // pointer; that could end up calling its destructor twice. Fortunately, // we don't need to hold its reference count to ensure it doesn't destruct // while we process this loop; as long as we ensure that no *other* // TransformState objects destruct, there will be no reason for that one // to. TransformState *other = (TransformState *)_composition_cache.get_key(i); // We hold a copy of the composition result so we can dereference it // later. Composition comp = _composition_cache.get_data(i); // Now we can remove the element from our cache. We do this now, rather // than later, before any other TransformState objects have had a chance // to destruct, so we are confident that our iterator is still valid. _composition_cache.remove_element(i); _cache_stats.add_total_size(-1); _cache_stats.inc_dels(); if (other != this) { int oi = other->_composition_cache.find(this); // We may or may not still be listed in the other's cache (it might be // halfway through pulling entries out, from within its own destructor). if (oi != -1) { // Hold a copy of the other composition result, too. Composition ocomp = other->_composition_cache.get_data(oi); other->_composition_cache.remove_element(oi); _cache_stats.add_total_size(-1); _cache_stats.inc_dels(); // It's finally safe to let our held pointers go away. This may have // cascading effects as other TransformState objects are destructed, // but there will be no harm done if they destruct now. if (ocomp._result != nullptr && ocomp._result != other) { cache_unref_delete(ocomp._result); } } } // It's finally safe to let our held pointers go away. (See comment // above.) if (comp._result != nullptr && comp._result != this) { cache_unref_delete(comp._result); } } // A similar bit of code for the invert cache. i = 0; while (!_invert_composition_cache.is_empty()) { TransformState *other = (TransformState *)_invert_composition_cache.get_key(i); nassertv(other != this); Composition comp = _invert_composition_cache.get_data(i); _invert_composition_cache.remove_element(i); _cache_stats.add_total_size(-1); _cache_stats.inc_dels(); if (other != this) { int oi = other->_invert_composition_cache.find(this); if (oi != -1) { Composition ocomp = other->_invert_composition_cache.get_data(oi); other->_invert_composition_cache.remove_element(oi); _cache_stats.add_total_size(-1); _cache_stats.inc_dels(); if (ocomp._result != nullptr && ocomp._result != other) { cache_unref_delete(ocomp._result); } } } if (comp._result != nullptr && comp._result != this) { cache_unref_delete(comp._result); } } } /** * Computes a suitable hash value for phash_map. */ void TransformState:: calc_hash() const { // It's OK not to grab the lock here, because (1) this does not depend on // cached values (only given components are considered), and (2) the hash // itself is set atomically. PStatTimer timer(_transform_hash_pcollector); AtomicAdjust::Integer hash = 0; static const int significant_flags = (F_is_invalid | F_is_identity | F_components_given | F_hpr_given | F_is_2d); int flags = (_flags & significant_flags); hash = int_hash::add_hash(hash, flags); nassertv((flags & (F_is_invalid | F_is_identity)) == 0); // Only bother to put the rest of the stuff in the hash if the transform // is not invalid or empty. if ((_flags & F_components_given) != 0) { // If the transform was specified componentwise, hash it componentwise. hash = _pos.add_hash(hash); if ((_flags & F_hpr_given) != 0) { hash = _hpr.add_hash(hash); } else if ((_flags & F_quat_given) != 0) { hash = _quat.add_hash(hash); } hash = _scale.add_hash(hash); hash = _shear.add_hash(hash); } else { // Otherwise, hash the matrix . . . if (_uniquify_matrix) { // . . . but only if the user thinks that's worthwhile. check_mat(); hash = _mat.add_hash(hash); } else { // Otherwise, hash the pointer only--any two different matrix-based // TransformStates are considered to be different, even if their // matrices have the same values. hash = pointer_hash::add_hash(hash, this); } } if (hash == H_unknown || hash == H_identity || hash == H_invalid) { // Arbitrarily offset the hash not to conflict with these special values. hash += 0x10000; } // We don't care if some other thread set this in the meantime, since every // thread should have computed the same hash. AtomicAdjust::set(_hash, hash); } /** * Determines whether the transform is singular (i.e. it scales to zero, and * has no inverse). */ void TransformState:: calc_singular() { LightMutexHolder holder(_lock); if ((_flags & F_singular_known) != 0) { // Someone else computed it first. return; } PStatTimer timer(_transform_calc_pcollector); nassertv((_flags & (F_is_invalid | F_is_identity)) == 0); // We determine if a matrix is singular by attempting to invert it (and we // save the result of this invert operation for a subsequent // do_invert_compose() call, which is almost certain to be made if someone // is asking whether we're singular). // This should be NULL if no one has called calc_singular() yet. nassertv(_inv_mat == nullptr); _inv_mat = new LMatrix4; if ((_flags & F_mat_known) == 0) { do_calc_mat(); } bool inverted = _inv_mat->invert_from(_mat); if (!inverted) { _flags |= F_is_singular; delete _inv_mat; _inv_mat = nullptr; } _flags |= F_singular_known; } /** * This is the implementation of calc_components(); it assumes the lock is * already held. */ void TransformState:: do_calc_components() { if ((_flags & F_components_known) != 0) { // Someone else computed it first. return; } PStatTimer timer(_transform_calc_pcollector); nassertv((_flags & (F_is_invalid | F_is_identity)) == 0); // If we don't have components and we're not identity, the only other // explanation is that we were constructed via a matrix. nassertv((_flags & F_mat_known) != 0); if ((_flags & F_mat_known) == 0) { do_calc_mat(); } bool possible = decompose_matrix(_mat, _scale, _shear, _hpr, _pos); if (!possible) { // Some matrices can't be decomposed into scale, hpr, pos. In this // case, we now know that we cannot compute the components; but the // closest approximations are stored, at least. _flags |= F_components_known | F_hpr_known; } else { // Otherwise, we do have the components, or at least the hpr. _flags |= F_has_components | F_components_known | F_hpr_known; check_uniform_scale(); } // However, we can always get at least the pos. _mat.get_row3(_pos, 3); } /** * This is the implementation of calc_hpr(); it assumes the lock is already * held. */ void TransformState:: do_calc_hpr() { if ((_flags & F_hpr_known) != 0) { // Someone else computed it first. return; } PStatTimer timer(_transform_calc_pcollector); nassertv((_flags & (F_is_invalid | F_is_identity)) == 0); if ((_flags & F_components_known) == 0) { do_calc_components(); } if ((_flags & F_hpr_known) == 0) { // If we don't know the hpr yet, we must have been given a quat. // Decompose it. nassertv((_flags & F_quat_known) != 0); _hpr = _quat.get_hpr(); _flags |= F_hpr_known; } } /** * Derives the quat from the hpr. */ void TransformState:: calc_quat() { LightMutexHolder holder(_lock); if ((_flags & F_quat_known) != 0) { // Someone else computed it first. return; } PStatTimer timer(_transform_calc_pcollector); nassertv((_flags & (F_is_invalid | F_is_identity)) == 0); if ((_flags & F_components_known) == 0) { do_calc_components(); } if ((_flags & F_quat_known) == 0) { // If we don't know the quat yet, we must have been given a hpr. // Decompose it. nassertv((_flags & F_hpr_known) != 0); _quat.set_hpr(_hpr); _flags |= F_quat_known; } } /** * Derives the normalized quat from the quat. */ void TransformState:: calc_norm_quat() { PStatTimer timer(_transform_calc_pcollector); LQuaternion quat = get_quat(); LightMutexHolder holder(_lock); _norm_quat = quat; _norm_quat.normalize(); _flags |= F_norm_quat_known; } /** * This is the implementation of calc_mat(); it assumes the lock is already * held. */ void TransformState:: do_calc_mat() { if ((_flags & F_mat_known) != 0) { // Someone else computed it first. return; } PStatTimer timer(_transform_calc_pcollector); nassertv((_flags & (F_is_invalid | F_is_identity)) == 0); // If we don't have a matrix and we're not identity, the only other // explanation is that we were constructed via components. nassertv((_flags & F_components_known) != 0); if ((_flags & F_hpr_known) == 0) { do_calc_hpr(); } compose_matrix(_mat, _scale, _shear, get_hpr(), _pos); _flags |= F_mat_known; } /** * Moves the TransformState object from one PStats category to another, so * that we can track in PStats how many pointers are held by nodes, and how * many are held in the cache only. */ void TransformState:: update_pstats(int old_referenced_bits, int new_referenced_bits) { #ifdef DO_PSTATS if ((old_referenced_bits & R_node) != 0) { _node_counter.sub_level(1); } else if ((old_referenced_bits & R_cache) != 0) { _cache_counter.sub_level(1); } if ((new_referenced_bits & R_node) != 0) { _node_counter.add_level(1); } else if ((new_referenced_bits & R_cache) != 0) { _cache_counter.add_level(1); } #endif // DO_PSTATS } /** * Tells the BamReader how to create objects of type TransformState. */ void TransformState:: register_with_read_factory() { BamReader::get_factory()->register_factory(get_class_type(), make_from_bam); } /** * Writes the contents of this object to the datagram for shipping out to a * Bam file. */ void TransformState:: write_datagram(BamWriter *manager, Datagram &dg) { TypedWritable::write_datagram(manager, dg); if ((_flags & F_is_identity) != 0) { // Identity, nothing much to that. int flags = F_is_identity | F_singular_known | F_is_2d; dg.add_uint32(flags); } else if ((_flags & F_is_invalid) != 0) { // Invalid, nothing much to that either. int flags = F_is_invalid | F_singular_known | F_is_singular | F_components_known | F_mat_known; dg.add_uint32(flags); } else if ((_flags & F_components_given) != 0) { // A component-based transform. int flags = F_components_given | F_components_known | F_has_components; flags |= (_flags & F_is_2d); if ((_flags & F_quat_given) != 0) { flags |= (F_quat_given | F_quat_known); } else if ((_flags & F_hpr_given) != 0) { flags |= (F_hpr_given | F_hpr_known); } dg.add_uint32(flags); _pos.write_datagram(dg); if ((_flags & F_quat_given) != 0) { _quat.write_datagram(dg); } else { get_hpr().write_datagram(dg); } _scale.write_datagram(dg); _shear.write_datagram(dg); } else { // A general matrix. nassertv((_flags & F_mat_known) != 0); int flags = F_mat_known; flags |= (_flags & F_is_2d); dg.add_uint32(flags); _mat.write_datagram(dg); } } /** * Called immediately after complete_pointers(), this gives the object a * chance to adjust its own pointer if desired. Most objects don't change * pointers after completion, but some need to. * * Once this function has been called, the old pointer will no longer be * accessed. */ PT(TypedWritableReferenceCount) TransformState:: change_this(TypedWritableReferenceCount *old_ptr, BamReader *manager) { // First, uniquify the pointer. TransformState *state = DCAST(TransformState, old_ptr); CPT(TransformState) pointer = return_unique(state); // We have to cast the pointer back to non-const, because the bam reader // expects that. return (TransformState *)pointer.p(); } /** * This function is called by the BamReader's factory when a new object of * type TransformState is encountered in the Bam file. It should create the * TransformState and extract its information from the file. */ TypedWritable *TransformState:: make_from_bam(const FactoryParams ¶ms) { TransformState *state = new TransformState; DatagramIterator scan; BamReader *manager; parse_params(params, scan, manager); state->fillin(scan, manager); if (state->_flags & F_is_identity) { delete state; return (TypedWritable *)_identity_state.p(); } else if (state->_flags & F_is_invalid) { delete state; return (TypedWritable *)_invalid_state.p(); } else { manager->register_change_this(change_this, state); } return state; } /** * This internal function is called by make_from_bam to read in all of the * relevant data from the BamFile for the new TransformState. */ void TransformState:: fillin(DatagramIterator &scan, BamReader *manager) { TypedWritable::fillin(scan, manager); _flags = scan.get_uint32(); if ((_flags & F_components_given) != 0) { // Componentwise transform. _pos.read_datagram(scan); if ((_flags & F_quat_given) != 0) { _quat.read_datagram(scan); } else { _hpr.read_datagram(scan); } _scale.read_datagram(scan); _shear.read_datagram(scan); check_uniform_scale(); } if ((_flags & F_mat_known) != 0) { // General matrix. _mat.read_datagram(scan); } }