> + std::for_each(toReinsert.begin(), toReinsert.end(), > + boost::bind(&DisplayList::reinsertRemovedCharacter, this, > _1)); > + I understand this is still under working. Just for a discussion. Is it too expensive to re-insert all removed characters? I think re-insertion is only needed for sprites. AFAIK, no other characters support onUnload/onLoad.
On 9/1/07, Sandro Santilli <[EMAIL PROTECTED]> wrote: > CVSROOT: /sources/gnash > Module name: gnash > Changes by: Sandro Santilli <strk> 07/09/01 01:20:47 > > Modified files: > . : ChangeLog > server : character.cpp character.h dlist.cpp dlist.h > movie_root.cpp movie_root.h sprite_instance.cpp > sprite_instance.h > testsuite/actionscript.all: MovieClip.as > testsuite/misc-ming.all: loop_test7.c loop_test8.c > unload_movieclip_test1.c > testsuite/misc-swfc.all: movieclip_destruction_test2.sc > testsuite/swfdec: PASSING > > Log message: > * server/dlist.{cpp,h}: when unloaded character's unload() > method > returns true (character or any of its childs has onUnload > methods to > be run) don't really remove it from the list, but just shift > it > down to the "removed" zone. Advance and display only > non-removed > characters (depth-zone based for now, possibly too weak). > Add lots of paranoid invariant testing (not enough, missing > the > one for no unloaded characters out of "removed" depth zone). > * server/character.{cpp,h}: more info about removedDepthOffset, > assertion preveing double unload of a character (this can be > probably easily broken by a focused testcase using > removeMovieClip > on a removed but still-reachable character). > * server/movie_root.{cpp,h}: added cleanupDisplayList() > method, and > call it at the end of actions execution to properly cleanup > removed > but still-reachable characters. > * server/sprite_instance.{cpp,h}: implement a > cleanupDisplayList, to > be called by movie_root, make sure to not include unloaded > characters when visiting the DisplayList for bounds > extractions and > similar. > * testsuite/actionscript.all/MovieClip.as: soft-references > successes > * testsuite/misc-ming.all/: loop_test7.c, loop_test8.c: > keep-alive-for-onUnload successes > * testsuite/misc-ming.all/unload_movieclip_test1.c: successes > * testsuite/misc-swfc.all/movieclip_destruction_test2.sc: > successes > * testsuite/swfdec/PASSING: remove-depths-*.swf successes > > CVSWeb URLs: > http://cvs.savannah.gnu.org/viewcvs/gnash/ChangeLog?cvsroot=gnash&r1=1.4179&r2=1.4180 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/character.cpp?cvsroot=gnash&r1=1.50&r2=1.51 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/character.h?cvsroot=gnash&r1=1.91&r2=1.92 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/dlist.cpp?cvsroot=gnash&r1=1.80&r2=1.81 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/dlist.h?cvsroot=gnash&r1=1.47&r2=1.48 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/movie_root.cpp?cvsroot=gnash&r1=1.82&r2=1.83 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/movie_root.h?cvsroot=gnash&r1=1.70&r2=1.71 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/sprite_instance.cpp?cvsroot=gnash&r1=1.319&r2=1.320 > http://cvs.savannah.gnu.org/viewcvs/gnash/server/sprite_instance.h?cvsroot=gnash&r1=1.134&r2=1.135 > http://cvs.savannah.gnu.org/viewcvs/gnash/testsuite/actionscript.all/MovieClip.as?cvsroot=gnash&r1=1.87&r2=1.88 > http://cvs.savannah.gnu.org/viewcvs/gnash/testsuite/misc-ming.all/loop_test7.c?cvsroot=gnash&r1=1.5&r2=1.6 > http://cvs.savannah.gnu.org/viewcvs/gnash/testsuite/misc-ming.all/loop_test8.c?cvsroot=gnash&r1=1.6&r2=1.7 > http://cvs.savannah.gnu.org/viewcvs/gnash/testsuite/misc-ming.all/unload_movieclip_test1.c?cvsroot=gnash&r1=1.3&r2=1.4 > http://cvs.savannah.gnu.org/viewcvs/gnash/testsuite/misc-swfc.all/movieclip_destruction_test2.sc?cvsroot=gnash&r1=1.5&r2=1.6 > http://cvs.savannah.gnu.org/viewcvs/gnash/testsuite/swfdec/PASSING?cvsroot=gnash&r1=1.27&r2=1.28 > > Patches: > Index: ChangeLog > =================================================================== > RCS file: /sources/gnash/gnash/ChangeLog,v > retrieving revision 1.4179 > retrieving revision 1.4180 > diff -u -b -r1.4179 -r1.4180 > --- ChangeLog 31 Aug 2007 22:16:49 -0000 1.4179 > +++ ChangeLog 1 Sep 2007 01:20:45 -0000 1.4180 > @@ -1,3 +1,30 @@ > +2007-09-01 Sandro Santilli <[EMAIL PROTECTED]> > + > + * server/dlist.{cpp,h}: when unloaded character's unload() method > + returns true (character or any of its childs has onUnload methods to > + be run) don't really remove it from the list, but just shift it > + down to the "removed" zone. Advance and display only non-removed > + characters (depth-zone based for now, possibly too weak). > + Add lots of paranoid invariant testing (not enough, missing the > + one for no unloaded characters out of "removed" depth zone). > + * server/character.{cpp,h}: more info about removedDepthOffset, > + assertion preveing double unload of a character (this can be > + probably easily broken by a focused testcase using removeMovieClip > + on a removed but still-reachable character). > + * server/movie_root.{cpp,h}: added cleanupDisplayList() method, and > + call it at the end of actions execution to properly cleanup removed > + but still-reachable characters. > + * server/sprite_instance.{cpp,h}: implement a cleanupDisplayList, to > + be called by movie_root, make sure to not include unloaded > + characters when visiting the DisplayList for bounds extractions and > + similar. > + * testsuite/actionscript.all/MovieClip.as: soft-references successes > + * testsuite/misc-ming.all/: loop_test7.c, loop_test8.c: > + keep-alive-for-onUnload successes > + * testsuite/misc-ming.all/unload_movieclip_test1.c: successes > + * testsuite/misc-swfc.all/movieclip_destruction_test2.sc: successes > + * testsuite/swfdec/PASSING: remove-depths-*.swf successes > + > 2007-08-31 Sandro Santilli <[EMAIL PROTECTED]> > > * server/asobj/gen-asclass.sh: add the getObjectInterface call. > > Index: server/character.cpp > =================================================================== > RCS file: /sources/gnash/gnash/server/character.cpp,v > retrieving revision 1.50 > retrieving revision 1.51 > diff -u -b -r1.50 -r1.51 > --- server/character.cpp 31 Aug 2007 15:40:05 -0000 1.50 > +++ server/character.cpp 1 Sep 2007 01:20:46 -0000 1.51 > @@ -17,7 +17,7 @@ > // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > // > > -/* $Id: character.cpp,v 1.50 2007/08/31 15:40:05 strk Exp $ */ > +/* $Id: character.cpp,v 1.51 2007/09/01 01:20:46 strk Exp $ */ > > #ifdef HAVE_CONFIG_H > #include "config.h" > @@ -676,8 +676,9 @@ > bool > character::unload() > { > - assert(!_unloaded); > + assert(!_unloaded); // don't unload characters twice ! > _unloaded = true; > + > //log_msg(_("Queuing unload event for character %p"), this); > return queueEventHandler(event_id::UNLOAD); > //on_event(event_id::UNLOAD); > > Index: server/character.h > =================================================================== > RCS file: /sources/gnash/gnash/server/character.h,v > retrieving revision 1.91 > retrieving revision 1.92 > diff -u -b -r1.91 -r1.92 > --- server/character.h 30 Aug 2007 14:13:07 -0000 1.91 > +++ server/character.h 1 Sep 2007 01:20:46 -0000 1.92 > @@ -19,7 +19,7 @@ > // > // > > -/* $Id: character.h,v 1.91 2007/08/30 14:13:07 strk Exp $ */ > +/* $Id: character.h,v 1.92 2007/09/01 01:20:46 strk Exp $ */ > > #ifndef GNASH_CHARACTER_H > #define GNASH_CHARACTER_H > @@ -350,6 +350,13 @@ > /// depth -32829 (-32769-60) when unloaded and > /// an onUnload event handler is defined for it. > /// > + /// So, to recap: > + /// 1: -32769 to -16385 are removed > + /// 2: -16384 to 0 are statics > + /// 3: Max depth for a PlaceoObject call is 16384 (which > becomes 0 in the statics) > + /// (all of the above correct?) > + /// > + /// > static const int removedDepthOffset = -32769; // -32769; > > /// This value is used for m_clip_depth when the value has no meaning, ie. > > Index: server/dlist.cpp > =================================================================== > RCS file: /sources/gnash/gnash/server/dlist.cpp,v > retrieving revision 1.80 > retrieving revision 1.81 > diff -u -b -r1.80 -r1.81 > --- server/dlist.cpp 31 Aug 2007 14:49:47 -0000 1.80 > +++ server/dlist.cpp 1 Sep 2007 01:20:46 -0000 1.81 > @@ -28,6 +28,7 @@ > #include <iostream> > #include <algorithm> > #include <stack> > +#include <boost/bind.hpp> > > namespace gnash { > > @@ -98,6 +99,8 @@ > int > DisplayList::getNextHighestDepth() const > { > + testInvariant(); > + > int nexthighestdepth=0; > for (const_iterator it = _characters.begin(), > itEnd = _characters.end(); > @@ -118,6 +121,8 @@ > character* > DisplayList::get_character_at_depth(int depth) > { > + testInvariant(); > + > //GNASH_REPORT_FUNCTION; > //dump(); > > @@ -143,6 +148,8 @@ > character* > DisplayList::get_character_by_name(const std::string& name) > { > + testInvariant(); > + > container_type::iterator it = find_if( > _characters.begin(), > _characters.end(), > @@ -156,6 +163,8 @@ > character* > DisplayList::get_character_by_name_i(const std::string& name) > { > + testInvariant(); > + > container_type::iterator it = find_if( > _characters.begin(), > _characters.end(), > @@ -182,6 +191,7 @@ > //dump(); > > assert(ch); > + assert(!ch->isUnloaded()); > ch->set_invalidated(); > ch->set_depth(depth); > ch->set_cxform(color_xform); > @@ -208,16 +218,23 @@ > InvalidatedRanges old_ranges; > (*it)->add_invalidated_bounds(old_ranges, true); > > - (*it)->unload(); > + boost::intrusive_ptr<character> oldCh = *it; > + bool hasUnloadEvent = oldCh->unload(); > + > // replace existing char > *it = DisplayItem(ch); > > + // reinsert removed character if needed > + if ( hasUnloadEvent ) reinsertRemovedCharacter(oldCh); > + > // extend invalidated bounds > ch->extend_invalidated_bounds(old_ranges); > } > > // Give life to this instance > ch->construct(); > + > + testInvariant(); > } > > void > @@ -236,17 +253,23 @@ > { > *it = DisplayItem(ch); > } > + > + testInvariant(); > } > > void > DisplayList::addAll(std::vector<character*>& chars, bool replace) > { > + testInvariant(); > + > for (std::vector<character*>::iterator it=chars.begin(), > itEnd=chars.end(); > it != itEnd; ++it) > { > add(*it, replace); > } > + > + testInvariant(); > } > > void > @@ -258,7 +281,11 @@ > int ratio, > int clip_depth) > { > + testInvariant(); > + > //GNASH_REPORT_FUNCTION; > + assert(ch); > + assert(!ch->isUnloaded()); > > ch->set_invalidated(); > ch->set_depth(depth); > @@ -292,7 +319,7 @@ > } > else > { > - character* oldch = it->get(); > + boost::intrusive_ptr<character> oldch = *it; > > InvalidatedRanges old_ranges; > > @@ -312,11 +339,14 @@ > oldch->add_invalidated_bounds(old_ranges, true); > > // Unload old char > - (*it)->unload(); > + bool hasUnloadEvent = oldch->unload(); > > // replace existing char > *it = di; > > + // reinsert removed character if needed > + if ( hasUnloadEvent ) reinsertRemovedCharacter(oldch); > + > // extend invalidated bounds > // WARNING: when a new Button character is added, > // the invalidated bounds computation will likely > @@ -330,6 +360,8 @@ > > // Give life to this instance > ch->construct(); > + > + testInvariant(); > } > > > @@ -343,6 +375,8 @@ > int ratio, > int /* clip_depth */) > { > + testInvariant(); > + > //GNASH_REPORT_FUNCTION; > //IF_VERBOSE_DEBUG(log_msg(_("dl::move(%d)"), depth)); > > @@ -358,6 +392,12 @@ > return; > } > > + if ( ch->isUnloaded() ) > + { > + log_error("Request to move an unloaded character"); > + assert(!ch->isUnloaded()); > + } > + > // TODO: is sign of depth related to accepting anim moves ? > if (ch->get_accept_anim_moves() == false) > { > @@ -378,6 +418,8 @@ > { > ch->set_ratio(ratio); > } > + > + testInvariant(); > } > > > @@ -387,6 +429,8 @@ > { > //GNASH_REPORT_FUNCTION; > > + testInvariant(); > + > //log_msg(_("Before removing, list is:")); > //dump(); > > @@ -394,6 +438,8 @@ > container_type::size_type size = _characters.size(); > #endif > > + // TODO: would it be legal to call remove_display_object with a depth > + // in the "removed" zone ? > // TODO: optimize to take by-depth order into account > container_type::iterator it = find_if( > _characters.begin(), > @@ -402,15 +448,25 @@ > > if ( it != _characters.end() ) > { > - (*it)->unload(); > + boost::intrusive_ptr<character> oldCh = *it; > + bool hasUnloadEvent = oldCh->unload(); > + > _characters.erase(it); > > + // reinsert removed character if needed > + // NOTE: could be optimized if we knew exactly how > + // to handle the case in which the target depth > + // (after the shift) is occupied already > + // > + if ( hasUnloadEvent ) reinsertRemovedCharacter(oldCh); > } > > #ifndef NDEBUG > assert(size >= _characters.size()); > #endif > > + testInvariant(); > + > //log_msg(_("Done removing, list is:")); > //dump(); > } > @@ -419,6 +475,7 @@ > void > DisplayList::swapDepths(character* ch1, int newdepth) > { > + testInvariant(); > > if ( newdepth < character::staticDepthOffset ) > { > @@ -429,8 +486,14 @@ > return; > } > > - assert(ch1->get_depth() != newdepth); > + int srcdepth = ch1->get_depth(); > + > + // what if source char is at a lower depth ? > + assert(srcdepth >= character::staticDepthOffset); > > + assert(srcdepth != newdepth); > + > + // TODO: optimize this scan by taking ch1 depth into account ? > container_type::iterator it1 = find(_characters.begin(), > _characters.end(), ch1); > > // upper bound ... > @@ -448,8 +511,6 @@ > { > DisplayItem ch2 = *it2; > > - int srcdepth = ch1->get_depth(); > - > ch2->set_depth(srcdepth); > > // TODO: we're not actually invalidated ourselves, rather our > parent is... > @@ -485,17 +546,14 @@ > // See displaylist_depths_test6.swf for more info. > ch1->transformedByScript(); > > -#ifndef NDEBUG > - // TODO: make this a testInvariant() method for DisplayList > - DisplayList sorted = *this; > - sorted.sort(); > - assert(*this == sorted); // check we didn't screw up ordering > -#endif > + testInvariant(); > > } > > void DisplayList::reset(movie_definition& movieDef, size_t tgtFrame, bool > call_unload) > { > + testInvariant(); > + > //GNASH_REPORT_FUNCTION; > > // 1. Find all "timeline depth" for the target frame, querying the > @@ -513,20 +571,24 @@ > cout << "Current DisplayList: " << *this << endl; > #endif > > - > typedef std::vector<int>::iterator SeekIter; > > SeekIter startSeek = save.begin(); > SeekIter endSeek = save.end(); > > - for (iterator it = _characters.begin(), itEnd = _characters.end(); it > != itEnd; ) > + std::vector<DisplayItem> toReinsert; > + > + iterator it = beginNonRemoved(_characters); > + for (iterator itEnd = _characters.end(); it != itEnd; ) > { > + testInvariant(); > + > DisplayItem& di = *it; > > int di_depth = di->get_depth(); > > /// We won't scan chars in the dynamic depth zone > - if ( di_depth >= 0 ) return; > + if ( di_depth >= 0 ) break; > > /// Always remove non-timeline instances ? > /// Seems so, at least for duplicateMovieClip > @@ -536,7 +598,13 @@ > if ( ! info ) > { > // Not to be saved, killing > - if ( call_unload ) di->unload(); > + if ( call_unload ) > + { > + if ( di->unload() ) > + { > + toReinsert.push_back(di); > + } > + } > it = _characters.erase(it); > continue; > } > @@ -546,6 +614,10 @@ > // we need to do this in some corner cases. > if(!di->isActionScriptReferenceable()) > { > + // TODO: no unload() call needed here ? would help GC > ? > + // (I guess there can't be any as_value pointing at > this > + // if it's not ActionScriptReferenceable after all...) > + // > it = _characters.erase(it); > continue; > } > @@ -555,7 +627,13 @@ > if( match == save.end()) > { > // Not to be saved, killing > - if ( call_unload ) di->unload(); > + if ( call_unload ) > + { > + if ( di->unload() ) > + { > + toReinsert.push_back(di); > + } > + } > it = _characters.erase(it); > continue; > } > @@ -570,23 +648,44 @@ > > ++it; > } > -} > > + testInvariant(); > + > + std::for_each(toReinsert.begin(), toReinsert.end(), > + boost::bind(&DisplayList::reinsertRemovedCharacter, this, > _1)); > + > + testInvariant(); > +} > > void > DisplayList::clear_except(const DisplayList& exclude, bool call_unload) > { > + //log_debug("clear_except(DislpayList, %d) called", call_unload); > //GNASH_REPORT_FUNCTION; > > + testInvariant(); > + //log_debug("First invariant test worked"); > + > + > assert(&exclude != this); > const container_type& keepchars = exclude._characters; > > + std::vector<DisplayItem> toReinsert; > + > + const_iterator keepStart = beginNonRemoved(keepchars); > + const_iterator keepEnd = keepchars.end(); > + > + //int called=0; > for (iterator it = _characters.begin(), itEnd = _characters.end(); it > != itEnd; ) > { > + > + testInvariant(); // TODO: expensive > + //log_debug("Invariant test in iteration %d worked", > called++); > + > DisplayItem& di = *it; > > bool is_affected = false; > - for (const_iterator kit = keepchars.begin(), kitEnd = > keepchars.end(); kit != kitEnd; ++kit) > + for (const_iterator kit = keepStart; kit != keepEnd; ++kit) > { > if ( *kit == di ) > { > @@ -597,12 +696,27 @@ > > if (is_affected == false) > { > - if ( call_unload ) di->unload(); > + if ( call_unload ) > + { > + if ( di->unload() ) > + { > + toReinsert.push_back(di); > + } > + } > it = _characters.erase(it); > continue; > } > it++; > } > + > + testInvariant(); > + //log_debug("Invariant test after cleanup worked"); > + > + std::for_each(toReinsert.begin(), toReinsert.end(), > + boost::bind(&DisplayList::reinsertRemovedCharacter, this, > _1)); > + > + testInvariant(); > + //log_debug("Invariant test after reinsertion worked"); > } > > void > @@ -610,8 +724,12 @@ > { > //GNASH_REPORT_FUNCTION; > > + testInvariant(); > + > const container_type dropchars = from._characters; > > + std::vector<DisplayItem> toReinsert; > + > for (iterator it = _characters.begin(), itEnd = _characters.end(); it > != itEnd; ) > { > DisplayItem& di = *it; > @@ -628,12 +746,25 @@ > > if (is_affected) > { > - if ( call_unload ) di->unload(); > + if ( call_unload ) > + { > + if ( di->unload() ) > + { > + toReinsert.push_back(di); > + } > + } > it = _characters.erase(it); > continue; > } > it++; > } > + > + testInvariant(); > + > + std::for_each(toReinsert.begin(), toReinsert.end(), > + boost::bind(&DisplayList::reinsertRemovedCharacter, this, > _1)); > + > + testInvariant(); > } > > void > @@ -642,14 +773,22 @@ > { > //GNASH_REPORT_FUNCTION; > > + testInvariant(); > + > // container_type::size_type size = _characters.size(); > > // That there was no crash gnash we iterate through the copy > std::list<DisplayItem> tmp_list = _characters; > > - for (iterator it = tmp_list.begin(), itEnd = tmp_list.end(); > - it != itEnd; ++it) > + // We only advance characters which are out of the "removed" zone > (or should we check isUnloaded?) > + // TODO: remove when copying _character to tmp_list directly ? > + iterator it = beginNonRemoved(tmp_list); > + //if ( it != tmp_list.end() ) log_debug("First non-removed char at > depth %d", (*it)->get_depth()); > + //iterator it = tmp_list.begin(); > + for (iterator itEnd = tmp_list.end(); it != itEnd; ++it) > { > + testInvariant(); // expensive !! > + > // @@@@ TODO FIX: If array changes size due to > // character actions, the iteration may not be > // correct! > @@ -684,9 +823,18 @@ > boost::intrusive_ptr<character> ch = *it; > assert(ch!=NULL); > > + if ( ch->isUnloaded() ) // debugging > + { > + log_error("character at depth %d is unloaded", > ch->get_depth()); > + abort(); > + } > + assert(! ch->isUnloaded() ); // we don't advance unloaded > chars > + > + > ch->advance(delta_time); > } > > + testInvariant(); > } > > > @@ -695,15 +843,27 @@ > void > DisplayList::display() > { > + testInvariant(); > + > //GNASH_REPORT_FUNCTION; > std::stack<int> clipDepthStack; > > - for( iterator it = _characters.begin(), endIt = _characters.end(); > - it != endIt; ++it) > + // We only advance characters which are out of the "removed" zone (or > should we check isUnloaded?) > + iterator it = beginNonRemoved(_characters); > + //iterator it = _characters.begin(); > + for(iterator endIt = _characters.end(); it != endIt; ++it) > { > character* ch = it->get(); > assert(ch); > > + if ( ch->isUnloaded() ) // debugging > + { > + log_error("character at depth %d is unloaded", > ch->get_depth()); > + abort(); > + } > + assert(! ch->isUnloaded() ); // we don't advance unloaded chars > + > + > // Check if this charater or any of its parents is a mask. > // Characters act as masks should always be rendered to the > // mask buffer despite their visibility. > @@ -761,26 +921,30 @@ > void > DisplayList::dump() const > { > + //testInvariant(); > + > int num=0; > for( const_iterator it = _characters.begin(), > endIt = _characters.end(); > it != endIt; ++it) > { > const DisplayItem& dobj = *it; > - log_msg(_("Item %d at depth %d (char id %d, name %s, type > %s"), > + log_msg(_("Item %d at depth %d (char id %d, name %s, type > %s)"), > num, dobj->get_depth(), dobj->get_id(), > - dobj->get_name().c_str(), typeid(*dobj).name()); > + dobj->get_name().c_str(), typeName(*dobj).c_str()); > num++; > } > } > > > void > -DisplayList::add_invalidated_bounds(InvalidatedRanges& ranges, bool force) { > +DisplayList::add_invalidated_bounds(InvalidatedRanges& ranges, bool force) > +{ > > - for( iterator it = _characters.begin(), > - endIt = _characters.end(); > - it != endIt; ++it) > + testInvariant(); > + > + iterator it = beginNonRemoved(_characters); > + for( iterator endIt = _characters.end(); it != endIt; ++it) > { > DisplayItem& dobj = *it; > #ifndef GNASH_USE_GC > @@ -825,6 +989,69 @@ > return os; > } > > +void > +DisplayList::reinsertRemovedCharacter(boost::intrusive_ptr<character> ch) > +{ > + assert(ch->isUnloaded()); > + > + // TODO: have this done by character::unload() instead ? > + int oldDepth = ch->get_depth(); > + int newDepth = character::removedDepthOffset - oldDepth; > + ch->set_depth(newDepth); > + > + testInvariant(); > + > + container_type::iterator it = find_if( > + _characters.begin(), _characters.end(), > + DepthGreaterOrEqual(newDepth)); > + if ( it == _characters.end() || (*it)->get_depth() != newDepth ) > + { > + // add the new char > + _characters.insert(it, DisplayItem(ch)); > + } > + else > + { > + // the character should not be in the displaylist already ! > + assert(it->get() != ch.get()); > + > + log_error("DisplayList::insertCharacter: target depth (%d) is > occupied, and we don't know what we're supposed to do - we'll avoid inserting > the character for now", newDepth); > + } > + > + testInvariant(); > +} > + > +/*private static*/ > +DisplayList::iterator > +DisplayList::beginNonRemoved(container_type& c) > +{ > + return std::find_if(c.begin(), c.end(), > + DepthGreaterOrEqual(character::removedDepthOffset - > character::staticDepthOffset)); > +} > + > +/*private static*/ > +DisplayList::const_iterator > +DisplayList::beginNonRemoved(const container_type& c) > +{ > + return std::find_if(c.begin(), c.end(), > DepthGreaterOrEqual(character::removedDepthOffset+1)); > +} > + > +void > +DisplayList::removeUnloaded() > +{ > + // TODO: erase from begin() to beginNonRemoved()-1 ? > + //log_debug("removeUnloaded called (dlist:%p)", (void*)this); > + testInvariant(); > + //log_debug(" first invTest passed, _characters have %d entries", > _characters.size()); > + //dump(); > + iterator last = std::remove_if(_characters.begin(), > _characters.end(), boost::bind(&character::isUnloaded, _1)); > + _characters.erase(last, _characters.end()); > + //log_debug(" After remove_if, _characters have %d entries - dumping > them", _characters.size()); > + //dump(); > + //log_debug(" Now testing invariant again"); > + testInvariant(); > + //log_debug(" second invTest passed"); > +} > + > } // namespace gnash > > > > Index: server/dlist.h > =================================================================== > RCS file: /sources/gnash/gnash/server/dlist.h,v > retrieving revision 1.47 > retrieving revision 1.48 > diff -u -b -r1.47 -r1.48 > --- server/dlist.h 30 Aug 2007 21:21:58 -0000 1.47 > +++ server/dlist.h 1 Sep 2007 01:20:46 -0000 1.48 > @@ -28,6 +28,10 @@ > > #include <list> > #include <iosfwd> > +#ifndef NDEBUG > +#include "log.h" > +#include <set> // for testInvariant > +#endif > > namespace gnash { > class cxform; > @@ -49,6 +53,27 @@ > > public: > > + void testInvariant() const > + { > +#ifndef NDEBUG > + DisplayList sorted = *this; > + // check no duplicated depths in list > + std::set<int> depths; > + for (const_iterator it=_characters.begin(), > itEnd=_characters.end(); it!=itEnd; ++it) > + { > + boost::intrusive_ptr<character> ch = *it; > + int depth = ch->get_depth(); > + if ( ! depths.insert(depth).second ) > + { > + log_debug("Depth %d is duplicated in > DisplayList %p", depth, (void*)this); > + abort(); > + } > + } > + sorted.sort(); > + assert(*this == sorted); // check we didn't screw up ordering > +#endif > + } > + > /// Output operator > friend std::ostream& operator<< (std::ostream&, const DisplayList&); > > @@ -172,6 +197,17 @@ > /// > void remove_display_object(int depth); > > + /// Remove all unloaded character from the list > + // > + /// Removed characters still in the list are those > + /// on which onUnload event handlers were defined.. > + /// > + /// NOTE: we don't call the function recursively in the > + /// contained elements, as that should not be needed > + /// (ie: any inned thing will not be accessible anyway) > + /// > + void removeUnloaded(); > + > /// Clear the display list. > void clear() > { > @@ -194,7 +230,7 @@ > > /// \brief > /// Clear all characters in this DisplayList except the ones > - /// contained in the given DisplayList > + /// contained in the given DisplayList and not unloaded > // > /// @param exclude > /// A DisplayList containing character instances to keep. > @@ -298,6 +334,11 @@ > /// The visitor functor will > /// receive a character pointer; must return true if > /// it wants next item or false to exit the loop. > + /// > + /// NOTE: all elements in the list are visited, even > + /// the removed ones (unloaded) > + /// TODO: inspect if worth providing an arg to skip removed > + /// > template <class V> > inline void visitForward(V& visitor); > > @@ -309,6 +350,11 @@ > /// will receive a character pointer; must return true if > /// it wants next item or false > /// to exit the loop. > + /// > + /// NOTE: all elements in the list are visited, even > + /// the removed ones (unloaded) > + /// TODO: inspect if worth providing an arg to skip removed > + /// > template <class V> > inline void visitBackward(V& visitor); > > @@ -320,6 +366,11 @@ > /// > /// The visitor functor will receive a character pointer, > /// it's return value is not used so can return void. > + /// > + /// NOTE: all elements in the list are visited, even > + /// the removed ones (unloaded) > + /// TODO: inspect if worth providing an arg to skip removed > + /// > template <class V> > inline void visitAll(V& visitor); > > @@ -379,6 +430,24 @@ > typedef container_type::reverse_iterator reverse_iterator; > typedef container_type::const_reverse_iterator const_reverse_iterator; > > + /// Return an iterator to the first element of the container NOT in > the "removed" depth zone > + static iterator beginNonRemoved(container_type& c); > + > + /// Return an iterator to the first element of the container NOT in > the "removed" depth zone > + static const_iterator beginNonRemoved(const container_type& c); > + > + /// Re-insert a removed-from-stage character after appropriately > + /// shifting its depth based on the character::removedDepthOffset > + /// value. > + // > + /// PRE-CONDITIONS > + /// - ch::isUnloaded() returns true (assertion fails otherwise) > + /// - ch is not already in the list (assertion fails otherwise) > + /// > + /// TODO: inspect what should happen if the target depth is already > occupied > + /// > + void reinsertRemovedCharacter(boost::intrusive_ptr<character> ch); > + > container_type _characters; > > > > Index: server/movie_root.cpp > =================================================================== > RCS file: /sources/gnash/gnash/server/movie_root.cpp,v > retrieving revision 1.82 > retrieving revision 1.83 > diff -u -b -r1.82 -r1.83 > --- server/movie_root.cpp 3 Aug 2007 21:44:32 -0000 1.82 > +++ server/movie_root.cpp 1 Sep 2007 01:20:46 -0000 1.83 > @@ -670,6 +670,10 @@ > #endif > processActionQueue(); > > + // Delete characters removed from the stage > + // from the display lists > + cleanupDisplayList(); > + > #ifdef GNASH_USE_GC > // Run the garbage collector (step back !!) > GC::get().collect(); > @@ -1197,6 +1201,18 @@ > } > > void > +movie_root::cleanupDisplayList() > +{ > + // scan a backup copy of the levels, so that movies advancement won't > + // invalidate iterators > + Levels cached = _movies; > + for (Levels::reverse_iterator i=cached.rbegin(), e=cached.rend(); > i!=e; ++i) > + { > + i->second->cleanupDisplayList(); > + } > +} > + > +void > movie_root::advanceMovie(boost::intrusive_ptr<sprite_instance> movie, float > delta_time) > { > #ifdef GNASH_DEBUG > > Index: server/movie_root.h > =================================================================== > RCS file: /sources/gnash/gnash/server/movie_root.h,v > retrieving revision 1.70 > retrieving revision 1.71 > diff -u -b -r1.70 -r1.71 > --- server/movie_root.h 2 Aug 2007 22:07:26 -0000 1.70 > +++ server/movie_root.h 1 Sep 2007 01:20:46 -0000 1.71 > @@ -15,7 +15,7 @@ > // along with this program; if not, write to the Free Software > // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > > -/* $Id: movie_root.h,v 1.70 2007/08/02 22:07:26 strk Exp $ */ > +/* $Id: movie_root.h,v 1.71 2007/09/01 01:20:46 strk Exp $ */ > > /// \page events_handling Handling of user events > /// > @@ -669,6 +669,10 @@ > // Advance all levels > void advanceAllLevels(float delta_time); > > + /// Delete characters removed from the stage > + /// from the display lists > + void cleanupDisplayList(); > + > // Advance a given level > void advanceMovie(boost::intrusive_ptr<sprite_instance> movie, float > delta_time); > > > Index: server/sprite_instance.cpp > =================================================================== > RCS file: /sources/gnash/gnash/server/sprite_instance.cpp,v > retrieving revision 1.319 > retrieving revision 1.320 > diff -u -b -r1.319 -r1.320 > --- server/sprite_instance.cpp 31 Aug 2007 21:53:31 -0000 1.319 > +++ server/sprite_instance.cpp 1 Sep 2007 01:20:46 -0000 1.320 > @@ -1630,6 +1630,8 @@ > {} > void operator() (character* ch) > { > + // don't include bounds of unloaded characters > + if ( ch->isUnloaded() ) return; > geometry::Range2d<float> chb = ch->getBounds(); > matrix m = ch->get_matrix(); > m.transform(chb); > @@ -1655,6 +1657,9 @@ > > void operator() (character* ch) > { > + // don't include bounds of unloaded characters > + if ( ch->isUnloaded() ) return; > + > // TODO: Are script-transformed object to be kept ? > // Need a testcase for this > //if ( ! ch->get_accept_anim_moves() ) > @@ -1681,10 +1686,12 @@ > unloadEvents(0) > {} > > - bool operator() (character* ch) > + void operator() (character* ch) > { > + // don't unload already unloaded characters > + if ( ch->isUnloaded() ) return; > + > if ( ch->unload() ) ++unloadEvents; > - return true; > } > > bool foundUnloadEvents() const > @@ -2325,6 +2332,8 @@ > // to need oldDisplayList again later, to extract the list of > // newly added characters > // > + //oldDisplayList.removeUnloaded(); // TODO: clean oldDisplayList here > instead than in cleanupDisplayList ? > + oldDisplayList.sort(); // this is to avoid failing assertions, since > we know characters might have changed depth... > DisplayList stillAlive = oldDisplayList; > stillAlive.clear_except(m_display_list, false); > //log_msg(_("Advancing %d pre-existing children of %s"), > stillAlive.size(), getTargetPath().c_str()); > @@ -2335,15 +2344,12 @@ > //log_msg(_("Executing actions in %s timeline"), > getTargetPath().c_str()); > do_actions(); > > - // Call UNLOAD event of just removed chars ! > - //DisplayList justRemoved = oldDisplayList; > - //justRemoved.clear_except(m_display_list, false); // true; > - // No, dont' call UNLOAD event, as it should be called by > remove_display_object! > - > - // Finally, execute actions in newly added childs > + // Finally, execute actions in (we actually "advance") newly added > + // (and not unloaded) childs > // > // These are elements in the current DisplayList, cleared > - // by all elements in oldDisplayList. > + // by all unloaded elements and by non-unloaded elements in > + // oldDisplayList. > // > // Of course we do NOT call UNLOAD events here, as > // the chars we're clearing have *not* been removed: > @@ -2351,6 +2357,7 @@ > // > DisplayList newlyAdded = m_display_list; > //log_msg(_("%s has %d current children and %d old children"), > getTargetPath().c_str(), m_display_list.size(), oldDisplayList.size()); > + newlyAdded.removeUnloaded(); > newlyAdded.clear(oldDisplayList, false); > //log_msg(_("Advancing %d newly-added (after clearing) children of > %s"), newlyAdded.size(), getTargetPath().c_str()); > newlyAdded.advance(delta_time); > @@ -3372,7 +3379,7 @@ > #endif > > UnloaderVisitor visitor; > - m_display_list.visitForward(visitor); > + m_display_list.visitAll(visitor); > > return character::unload() || visitor.foundUnloadEvents(); > > @@ -3628,6 +3635,9 @@ > > void operator() (character* ch) > { > + // don't enumerate unloaded characters > + if ( ch->isUnloaded() ) return; > + > _env.push(ch->get_name()); > } > }; > @@ -3639,6 +3649,14 @@ > m_display_list.visitAll(visitor); > } > > +void > +sprite_instance::cleanupDisplayList() > +{ > + //log_debug("%s.cleanDisplayList() called, current dlist is %p, old > is %p", getTarget().c_str(), (void*)&m_display_list, (void*)&oldDisplayList); > + m_display_list.removeUnloaded(); > + oldDisplayList.removeUnloaded(); // TODO: move unloaded-cleanup of > oldDisplayList in advance_sprite ? > +} > + > #ifdef GNASH_USE_GC > struct ReachableMarker { > void operator() (character *ch) > > Index: server/sprite_instance.h > =================================================================== > RCS file: /sources/gnash/gnash/server/sprite_instance.h,v > retrieving revision 1.134 > retrieving revision 1.135 > diff -u -b -r1.134 -r1.135 > --- server/sprite_instance.h 31 Aug 2007 07:56:11 -0000 1.134 > +++ server/sprite_instance.h 1 Sep 2007 01:20:47 -0000 1.135 > @@ -748,6 +748,10 @@ > return _origTarget; > } > > + /// Delete characters removed from the stage > + /// from the display lists > + void cleanupDisplayList(); > + > private: > > /// \brief > > Index: testsuite/actionscript.all/MovieClip.as > =================================================================== > RCS file: /sources/gnash/gnash/testsuite/actionscript.all/MovieClip.as,v > retrieving revision 1.87 > retrieving revision 1.88 > diff -u -b -r1.87 -r1.88 > --- testsuite/actionscript.all/MovieClip.as 31 Aug 2007 21:53:33 -0000 > 1.87 > +++ testsuite/actionscript.all/MovieClip.as 1 Sep 2007 01:20:47 -0000 > 1.88 > @@ -20,7 +20,7 @@ > // compile this test case with Ming makeswf, and then > // execute it like this gnash -1 -r 0 -v out.swf > > -rcsid="$Id: MovieClip.as,v 1.87 2007/08/31 21:53:33 strk Exp $"; > +rcsid="$Id: MovieClip.as,v 1.88 2007/09/01 01:20:47 strk Exp $"; > > #include "check.as" > > @@ -481,23 +481,23 @@ > #endif > > check_equals(typeof(hardref), 'undefined'); > -xcheck_equals(typeof(hardref2), 'movieclip'); > -xcheck_equals(typeof(hardref3), 'movieclip'); // still accessible due to > onUnload defined for its child > -xcheck_equals(hardref2.getDepth(), -32839); > -xcheck_equals(hardref3.getDepth(), -32849); > -xcheck_equals(hardref3.hardref3child.getDepth(), 1); > +check_equals(typeof(hardref2), 'movieclip'); > +check_equals(typeof(hardref3), 'movieclip'); // still accessible due to > onUnload defined for its child > +check_equals(hardref2.getDepth(), -32839); > +check_equals(hardref3.getDepth(), -32849); > +check_equals(hardref3.hardref3child.getDepth(), 1); > check_equals(typeof(softref), 'movieclip'); > check_equals(typeof(softref2), 'movieclip'); > check_equals(typeof(softref3), 'movieclip'); > check_equals(typeof(softref3child), 'movieclip'); > check_equals(typeof(softref.member), 'undefined'); > check_equals(typeof(softref._target), 'undefined'); > -xcheck_equals(softref2.member, 2); > -xcheck_equals(softref2._target, '/hardref2'); > -xcheck_equals(softref3.member, 3); > -xcheck_equals(softref3._target, '/hardref3'); > -xcheck_equals(softref3child.member, '3child'); > -xcheck_equals(softref3child._target, '/hardref3/hardref3child'); > +check_equals(softref2.member, 2); > +check_equals(softref2._target, '/hardref2'); > +check_equals(softref3.member, 3); > +check_equals(softref3._target, '/hardref3'); > +check_equals(softref3child.member, '3child'); > +check_equals(softref3child._target, '/hardref3/hardref3child'); > hardref = 4; > // Delete is needed, or further inspection functions will hit the variable > before the character > delete hardref; > > Index: testsuite/misc-ming.all/loop_test7.c > =================================================================== > RCS file: /sources/gnash/gnash/testsuite/misc-ming.all/loop_test7.c,v > retrieving revision 1.5 > retrieving revision 1.6 > diff -u -b -r1.5 -r1.6 > --- testsuite/misc-ming.all/loop_test7.c 1 Jul 2007 10:54:57 -0000 > 1.5 > +++ testsuite/misc-ming.all/loop_test7.c 1 Sep 2007 01:20:47 -0000 > 1.6 > @@ -122,7 +122,7 @@ > // RemoveObject2 is *before* the DoAction, then typeof(movieClip1) will > reurn 'undefined'. > // So Gnash fails here because of action execution order! > // TODO: add testcase for this(RemoveObject2 placed *before* DoAction > within the same frame). > - xcheck_equals(mo, "typeof(movieClip1)", "'movieclip'"); // kept alive for > calling onUnload! > + check_equals(mo, "typeof(movieClip1)", "'movieclip'"); // kept alive for > calling onUnload! > check_equals(mo, "_root.mc1Constructed", "1"); > SWFMovie_nextFrame(mo); > > @@ -133,7 +133,7 @@ > > check_equals(mo, "typeof(movieClip1)", "'undefined'"); > SWFMovie_add(mo, (SWFBlock)newSWFAction( "gotoAndStop(4);")); > - xcheck_equals(mo, "typeof(movieClip1)", "'movieclip'"); > + check_equals(mo, "typeof(movieClip1)", "'movieclip'"); > > // onConstruct is called twice > check_equals(mo, "_root.mc1Constructed", "2"); > > Index: testsuite/misc-ming.all/loop_test8.c > =================================================================== > RCS file: /sources/gnash/gnash/testsuite/misc-ming.all/loop_test8.c,v > retrieving revision 1.6 > retrieving revision 1.7 > diff -u -b -r1.6 -r1.7 > --- testsuite/misc-ming.all/loop_test8.c 1 Jul 2007 10:54:57 -0000 > 1.6 > +++ testsuite/misc-ming.all/loop_test8.c 1 Sep 2007 01:20:47 -0000 > 1.7 > @@ -221,7 +221,7 @@ > check_equals(mo, "typeof(mc2)", "'undefined'"); > check_equals(mo, "typeof(mc3)", "'movieclip'"); > check_equals(mo, "typeof(mc4)", "'movieclip'"); > - xcheck_equals(mo, "typeof(mc5)", "'movieclip'"); // Gnash fails because of > action execution order > + check_equals(mo, "typeof(mc5)", "'movieclip'"); // Gnash fails because of > action execution order > check_equals(mo, "mc1Constructed", "1"); > check_equals(mo, "mc2Constructed", "1"); > check_equals(mo, "mc3Constructed", "2"); > > Index: testsuite/misc-ming.all/unload_movieclip_test1.c > =================================================================== > RCS file: > /sources/gnash/gnash/testsuite/misc-ming.all/unload_movieclip_test1.c,v > retrieving revision 1.3 > retrieving revision 1.4 > diff -u -b -r1.3 -r1.4 > --- testsuite/misc-ming.all/unload_movieclip_test1.c 20 Jul 2007 02:02:08 > -0000 1.3 > +++ testsuite/misc-ming.all/unload_movieclip_test1.c 1 Sep 2007 01:20:47 > -0000 1.4 > @@ -88,7 +88,7 @@ > add_actions(mo, "mc.onUnload = function () { " > " _root.x = this._currentframe; " > " _root.check_equals(typeof(this), 'movieclip'); " > - " _root.xcheck_equals(this, _root.mc); " > + " _root.check_equals(this, _root.mc); " > "};"); > SWFMovie_nextFrame(mo); > > @@ -99,7 +99,7 @@ > > // Frame 4: checks > > - xcheck_equals(mo, "_root.x", "1"); > + check_equals(mo, "_root.x", "1"); > > add_actions(mo, "_root.totals(); stop(); "); > SWFMovie_nextFrame(mo); > > Index: testsuite/misc-swfc.all/movieclip_destruction_test2.sc > =================================================================== > RCS file: > /sources/gnash/gnash/testsuite/misc-swfc.all/movieclip_destruction_test2.sc,v > retrieving revision 1.5 > retrieving revision 1.6 > diff -u -b -r1.5 -r1.6 > --- testsuite/misc-swfc.all/movieclip_destruction_test2.sc 31 Aug 2007 > 14:49:48 -0000 1.5 > +++ testsuite/misc-swfc.all/movieclip_destruction_test2.sc 1 Sep 2007 > 01:20:47 -0000 1.6 > @@ -87,12 +87,12 @@ > { > _root.mc2UnlaodedCount++; > // mc2.testvar keeps alive as long as mc2 is alive > - _root.xcheck_equals(mc2.testvar, 100); > + _root.check_equals(mc2.testvar, 100); > }; > mc3.onUnload = function () > { > _root.mc3UnlaodedCount++; > - _root.xcheck_equals(mc3.testvar, 100); > + _root.check_equals(mc3.testvar, 100); > }; > > mc2.testvar = 100; > @@ -114,38 +114,38 @@ > xcheck_equals(mc2UnlaodedCount, 1); //mc2.onUnload triggered > xcheck_equals(mc2UnlaodedCount, 1); //mc3.onUnload triggered > check_equals(mc1Ref.valueOf(), null); > - xcheck_equals(mc2Ref, mc2); > - xcheck_equals(mc3Ref, mc3); > + check_equals(mc2Ref, mc2); > + check_equals(mc3Ref, mc3); > > check_equals(typeof(mc1), 'undefined'); //cann't access the hard reference > - xcheck_equals(typeof(mc2), 'movieclip'); // mc2 is still accessable > - xcheck_equals(typeof(mc3), 'movieclip'); // mc3 is still accessable > - xcheck_equals(mc2.getDepth(), -16387); // depth of mc2 changed after > onUnload > - xcheck_equals(mc3.getDepth(), -16388); // depth of mc3 changed after > onUnload > + check_equals(typeof(mc2), 'movieclip'); // mc2 is still accessable > + check_equals(typeof(mc3), 'movieclip'); // mc3 is still accessable > + check_equals(mc2.getDepth(), -16387); // depth of mc2 changed after > onUnload > + check_equals(mc3.getDepth(), -16388); // depth of mc3 changed after > onUnload > > mc2.swapDepths(mc3); > - xcheck_equals(mc2.getDepth(), -16387); // depth not change after > swapDepths > - xcheck_equals(mc3.getDepth(), -16388); // depth not change after > swapDepths > + check_equals(mc2.getDepth(), -16387); // depth not change after > swapDepths > + check_equals(mc3.getDepth(), -16388); // depth not change after > swapDepths > > mc2.swapDephts(-10); > mc2.swapDephts(10); > - xcheck_equals(mc2.getDepth(), -16387); // depth not change after > swapDepths > - xcheck_equals(mc3.getDepth(), -16388); // depth not change after > swapDepths > + check_equals(mc2.getDepth(), -16387); // depth not change after > swapDepths > + check_equals(mc3.getDepth(), -16388); // depth not change after > swapDepths > > - xcheck_equals(mc2.testvar, 100); > - xcheck_equals(mc3.testvar, 100); > + check_equals(mc2.testvar, 100); > + check_equals(mc3.testvar, 100); > mc2.removMovieClip(); > mc3.removMovieClip(); > xcheck_equals(mc2UnlaodedCount, 1); //mc2.onUnload not triggered again > xcheck_equals(mc2UnlaodedCount, 1); //mc3.onUnload not triggered again > - xcheck_equals(typeof(mc2), 'movieclip'); // mc2 is still accessible > - xcheck_equals(typeof(mc3), 'movieclip'); // mc3 is still accessible > - xcheck_equals(mc2.getDepth(), -16387); > - xcheck_equals(mc3.getDepth(), -16388); > - xcheck_equals(mc2._x, 200); > - xcheck_equals(mc3._y, 300); > - xcheck_equals(mc2.testvar, 100); > - xcheck_equals(mc3.testvar, 100); > + check_equals(typeof(mc2), 'movieclip'); // mc2 is still accessible > + check_equals(typeof(mc3), 'movieclip'); // mc3 is still accessible > + check_equals(mc2.getDepth(), -16387); > + check_equals(mc3.getDepth(), -16388); > + check_equals(mc2._x, 200); > + check_equals(mc3._y, 300); > + check_equals(mc2.testvar, 100); > + check_equals(mc3.testvar, 100); > > mc2.onUnload(); > mc3.onUnload(); > > Index: testsuite/swfdec/PASSING > =================================================================== > RCS file: /sources/gnash/gnash/testsuite/swfdec/PASSING,v > retrieving revision 1.27 > retrieving revision 1.28 > diff -u -b -r1.27 -r1.28 > --- testsuite/swfdec/PASSING 31 Aug 2007 21:53:33 -0000 1.27 > +++ testsuite/swfdec/PASSING 1 Sep 2007 01:20:47 -0000 1.28 > @@ -193,3 +193,6 @@ > with-outobject-6.swf > with-outobject-7.swf > with-outobject-8.swf > +remove-depths-6.swf > +remove-depths-7.swf > +remove-depths-8.swf > > > _______________________________________________ > Gnash-commit mailing list > Gnash-commit@gnu.org > http://lists.gnu.org/mailman/listinfo/gnash-commit > _______________________________________________ Gnash-commit mailing list Gnash-commit@gnu.org http://lists.gnu.org/mailman/listinfo/gnash-commit