Panda3D
|
00001 // Filename: bamWriter.cxx 00002 // Created by: jason (08Jun00) 00003 // 00004 //////////////////////////////////////////////////////////////////// 00005 // 00006 // PANDA 3D SOFTWARE 00007 // Copyright (c) Carnegie Mellon University. All rights reserved. 00008 // 00009 // All use of this software is subject to the terms of the revised BSD 00010 // license. You should have received a copy of this license along 00011 // with this source code in a file named "LICENSE." 00012 // 00013 //////////////////////////////////////////////////////////////////// 00014 00015 #include "pandabase.h" 00016 #include "pnotify.h" 00017 00018 #include "typedWritable.h" 00019 #include "config_util.h" 00020 #include "bam.h" 00021 #include "bamWriter.h" 00022 #include "bamReader.h" 00023 #include "lightMutexHolder.h" 00024 00025 #include <algorithm> 00026 00027 //////////////////////////////////////////////////////////////////// 00028 // Function: BamWriter::Constructor 00029 // Access: Published 00030 // Description: 00031 //////////////////////////////////////////////////////////////////// 00032 BamWriter:: 00033 BamWriter(DatagramSink *target, const Filename &name) : 00034 _filename(name), 00035 _target(target) 00036 { 00037 ++_writing_seq; 00038 _next_boc = BOC_adjunct; 00039 _needs_init = true; 00040 00041 // Initialize the next object and PTA ID's. These start counting at 00042 // 1, since 0 is reserved for NULL. 00043 _next_object_id = 1; 00044 _long_object_id = false; 00045 _next_pta_id = 1; 00046 _long_pta_id = false; 00047 00048 _file_endian = bam_endian; 00049 _file_texture_mode = bam_texture_mode; 00050 } 00051 00052 //////////////////////////////////////////////////////////////////// 00053 // Function: BamWriter::Destructor 00054 // Access: Published 00055 // Description: 00056 //////////////////////////////////////////////////////////////////// 00057 BamWriter:: 00058 ~BamWriter() { 00059 // Tell all the TypedWritables whose pointer we are still keeping to 00060 // forget about us. 00061 StateMap::iterator si; 00062 for (si = _state_map.begin(); si != _state_map.end(); ++si) { 00063 TypedWritable *object = (TypedWritable *)(*si).first; 00064 LightMutexHolder holder(TypedWritable::_bam_writers_lock); 00065 nassertv(object->_bam_writers != (TypedWritable::BamWriters *)NULL); 00066 TypedWritable::BamWriters::iterator wi = 00067 find(object->_bam_writers->begin(), object->_bam_writers->end(), this); 00068 nassertv(wi != object->_bam_writers->end()); 00069 object->_bam_writers->erase(wi); 00070 } 00071 } 00072 00073 //////////////////////////////////////////////////////////////////// 00074 // Function: BamWriter::set_target 00075 // Access: Published 00076 // Description: Changes the destination of future datagrams written 00077 // by the BamWriter. This also implicitly calls init() 00078 // if it has not already been called. 00079 //////////////////////////////////////////////////////////////////// 00080 void BamWriter:: 00081 set_target(DatagramSink *target) { 00082 if (_target != NULL) { 00083 _target->flush(); 00084 } 00085 _target = target; 00086 00087 if (_needs_init && _target != NULL) { 00088 init(); 00089 } 00090 } 00091 00092 //////////////////////////////////////////////////////////////////// 00093 // Function: BamWriter::init 00094 // Access: Published 00095 // Description: Initializes the BamWriter prior to writing any 00096 // objects to its output stream. This includes writing 00097 // out the Bam header. 00098 // 00099 // This returns true if the BamWriter successfully 00100 // initialized, false otherwise. 00101 //////////////////////////////////////////////////////////////////// 00102 bool BamWriter:: 00103 init() { 00104 nassertr(_target != NULL, false); 00105 nassertr(_needs_init, false); 00106 _needs_init = false; 00107 00108 // Initialize the next object and PTA ID's. These start counting at 00109 // 1, since 0 is reserved for NULL. 00110 _next_object_id = 1; 00111 _long_object_id = false; 00112 _next_pta_id = 1; 00113 _long_pta_id = false; 00114 00115 _file_endian = bam_endian; 00116 _file_texture_mode = bam_texture_mode; 00117 00118 // Write out the current major and minor BAM file version numbers. 00119 Datagram header; 00120 00121 header.add_uint16(_bam_major_ver); 00122 header.add_uint16(_bam_minor_ver); 00123 header.add_uint8(_file_endian); 00124 00125 if (!_target->put_datagram(header)) { 00126 util_cat.error() 00127 << "Unable to write Bam header.\n"; 00128 return false; 00129 } 00130 return true; 00131 } 00132 00133 //////////////////////////////////////////////////////////////////// 00134 // Function: BamWriter::write_object 00135 // Access: Published 00136 // Description: Writes a single object to the Bam file, so that the 00137 // BamReader::read_object() can later correctly restore 00138 // the object and all its pointers. 00139 // 00140 // This implicitly also writes any additional objects 00141 // this object references (if they haven't already been 00142 // written), so that pointers may be fully resolved. 00143 // 00144 // This may be called repeatedly to write a sequence of 00145 // objects to the Bam file, but typically (especially 00146 // for scene graph files, indicated with the .bam 00147 // extension), only one object is written directly from 00148 // the Bam file: the root of the scene graph. The 00149 // remaining objects will all be written recursively by 00150 // the first object. 00151 // 00152 // Returns true if the object is successfully written, 00153 // false otherwise. 00154 //////////////////////////////////////////////////////////////////// 00155 bool BamWriter:: 00156 write_object(const TypedWritable *object) { 00157 nassertr(_target != NULL, false); 00158 00159 // Increment the _writing_seq, so we can check for newly stale 00160 // objects during this operation. 00161 ++_writing_seq; 00162 00163 // If there are any freed objects to indicate, write them out now. 00164 if (!_freed_object_ids.empty()) { 00165 Datagram dg; 00166 dg.add_uint8(BOC_remove); 00167 00168 FreedObjectIds::iterator fi; 00169 for (fi = _freed_object_ids.begin(); fi != _freed_object_ids.end(); ++fi) { 00170 write_object_id(dg, (*fi)); 00171 } 00172 _freed_object_ids.clear(); 00173 00174 if (!_target->put_datagram(dg)) { 00175 util_cat.error() 00176 << "Unable to write data to output.\n"; 00177 return false; 00178 } 00179 } 00180 00181 nassertr(_object_queue.empty(), false); 00182 _next_boc = BOC_push; 00183 00184 int object_id = enqueue_object(object); 00185 nassertr(object_id != 0, false); 00186 if (!flush_queue()) { 00187 return false; 00188 } 00189 00190 // Finally, write the closing pop. 00191 if (_next_boc != BOC_push) { 00192 Datagram dg; 00193 dg.add_uint8(BOC_pop); 00194 if (!_target->put_datagram(dg)) { 00195 util_cat.error() 00196 << "Unable to write data to output.\n"; 00197 return false; 00198 } 00199 } 00200 00201 return true; 00202 } 00203 00204 //////////////////////////////////////////////////////////////////// 00205 // Function: BamWriter::has_object 00206 // Access: Published 00207 // Description: Returns true if the object has previously been 00208 // written (or at least requested to be written) to the 00209 // bam file, or false if we've never heard of it before. 00210 //////////////////////////////////////////////////////////////////// 00211 bool BamWriter:: 00212 has_object(const TypedWritable *object) const { 00213 StateMap::const_iterator si = _state_map.find(object); 00214 return (si != _state_map.end()); 00215 } 00216 00217 //////////////////////////////////////////////////////////////////// 00218 // Function: BamWriter::flush 00219 // Access: Published 00220 // Description: Ensures that all data written thus far is manifested 00221 // on the output stream. 00222 //////////////////////////////////////////////////////////////////// 00223 void BamWriter:: 00224 flush() { 00225 nassertv(_target != NULL); 00226 _target->flush(); 00227 } 00228 00229 //////////////////////////////////////////////////////////////////// 00230 // Function: BamWriter::consider_update 00231 // Access: Public 00232 // Description: Should be called from 00233 // TypedWritable::update_bam_nested() to recursively 00234 // check the entire hiererachy of writable objects for 00235 // needed updates. This tests the indicated 00236 // TypedWritable object and writes it to the bam stream 00237 // if it has recently been modified, then recurses 00238 // through update_bam_nested. 00239 //////////////////////////////////////////////////////////////////// 00240 void BamWriter:: 00241 consider_update(const TypedWritable *object) { 00242 StateMap::iterator si = _state_map.find(object); 00243 if (si == _state_map.end()) { 00244 // This object has never even been seen before. 00245 enqueue_object(object); 00246 00247 } else if ((*si).second._written_seq.is_initial()) { 00248 // This object has not been written yet. 00249 enqueue_object(object); 00250 00251 } else if ((*si).second._written_seq == _writing_seq) { 00252 // We have already visited this object this pass, so no need to 00253 // look closer. 00254 00255 } else if ((*si).second._modified != object->get_bam_modified()) { 00256 // This object has been recently modified and needs to be rewritten. 00257 enqueue_object(object); 00258 00259 } else { 00260 // Mark that we have now visited this object and pronounced it clean. 00261 (*si).second._written_seq = _writing_seq; 00262 00263 // Recurse to child objects. 00264 ((TypedWritable *)object)->update_bam_nested(this); 00265 } 00266 } 00267 00268 //////////////////////////////////////////////////////////////////// 00269 // Function: BamWriter::write_pointer 00270 // Access: Public 00271 // Description: The interface for writing a pointer to another object 00272 // to a Bam file. This is intended to be called by the 00273 // various objects that write themselves to the Bam 00274 // file, within the write_datagram() method. 00275 // 00276 // This writes the pointer out in such a way that the 00277 // BamReader will be able to restore the pointer later. 00278 // If the pointer is to an object that has not yet 00279 // itself been written to the Bam file, that object will 00280 // automatically be written. 00281 //////////////////////////////////////////////////////////////////// 00282 void BamWriter:: 00283 write_pointer(Datagram &packet, const TypedWritable *object) { 00284 // If the pointer is NULL, we always simply write a zero for an 00285 // object ID and leave it at that. 00286 if (object == (const TypedWritable *)NULL) { 00287 write_object_id(packet, 0); 00288 00289 } else { 00290 StateMap::iterator si = _state_map.find(object); 00291 if (si == _state_map.end()) { 00292 // We have not written this pointer out yet. This means we must 00293 // queue the object definition up for later. 00294 int object_id = enqueue_object(object); 00295 write_object_id(packet, object_id); 00296 00297 } else { 00298 // We have already assigned this pointer an ID, so it has 00299 // previously been written; but we might still need to rewrite 00300 // it if it is stale. 00301 int object_id = (*si).second._object_id; 00302 bool already_written = !(*si).second._written_seq.is_initial(); 00303 if ((*si).second._written_seq != _writing_seq && 00304 (*si).second._modified != object->get_bam_modified()) { 00305 // This object was previously written, but it has since been 00306 // modified, so we should write it again. 00307 already_written = false; 00308 } 00309 00310 write_object_id(packet, object_id); 00311 00312 if (!already_written) { 00313 // It's stale, so queue the object for rewriting too. 00314 enqueue_object(object); 00315 } else { 00316 // Not stale, but maybe its child object is. 00317 ((TypedWritable *)object)->update_bam_nested(this); 00318 } 00319 } 00320 } 00321 } 00322 00323 //////////////////////////////////////////////////////////////////// 00324 // Function: BamWriter::write_cdata 00325 // Access: Public 00326 // Description: Writes out the indicated CycleData object. This 00327 // should be used by classes that store some or all of 00328 // their data within a CycleData subclass, in support of 00329 // pipelining. This will call the virtual 00330 // CycleData::write_datagram() method to do the actual 00331 // writing. 00332 //////////////////////////////////////////////////////////////////// 00333 void BamWriter:: 00334 write_cdata(Datagram &packet, const PipelineCyclerBase &cycler) { 00335 const CycleData *cdata = cycler.read(Thread::get_current_thread()); 00336 cdata->write_datagram(this, packet); 00337 cycler.release_read(cdata); 00338 } 00339 00340 //////////////////////////////////////////////////////////////////// 00341 // Function: BamWriter::write_cdata 00342 // Access: Public 00343 // Description: This version of write_cdata allows passing an 00344 // additional parameter to cdata->write_datagram(). 00345 //////////////////////////////////////////////////////////////////// 00346 void BamWriter:: 00347 write_cdata(Datagram &packet, const PipelineCyclerBase &cycler, 00348 void *extra_data) { 00349 const CycleData *cdata = cycler.read(Thread::get_current_thread()); 00350 cdata->write_datagram(this, packet, extra_data); 00351 cycler.release_read(cdata); 00352 } 00353 00354 //////////////////////////////////////////////////////////////////// 00355 // Function: BamWriter::register_pta 00356 // Access: Public 00357 // Description: Prepares to write a PointerToArray to the Bam file, 00358 // unifying references to the same pointer across the 00359 // Bam file. 00360 // 00361 // The writing object should call this prior to writing 00362 // out a PointerToArray. It will return true if the 00363 // same pointer has been written previously, in which 00364 // case the writing object need do nothing further; or 00365 // it will return false if this particular pointer has 00366 // not yet been written, in which case the writing 00367 // object must then write out the contents of the array. 00368 // 00369 // Also see the WRITE_PTA() macro, which consolidates 00370 // the work that must be done to write a PTA. 00371 //////////////////////////////////////////////////////////////////// 00372 bool BamWriter:: 00373 register_pta(Datagram &packet, const void *ptr) { 00374 if (ptr == (const void *)NULL) { 00375 // A zero for the PTA ID indicates a NULL pointer. This is a 00376 // special case. 00377 write_pta_id(packet, 0); 00378 00379 // We return false to indicate the user must now write out the 00380 // "definition" of the NULL pointer. This is necessary because of 00381 // a quirk in the BamReader's design, which forces callers to read 00382 // the definition of every NULL pointer. Presumably, the caller 00383 // will be able to write the definition in a concise way that will 00384 // clearly indicate a NULL pointer; in the case of a 00385 // PointerToArray, this will generally be simply a zero element 00386 // count. 00387 return false; 00388 } 00389 00390 PTAMap::iterator pi = _pta_map.find(ptr); 00391 if (pi == _pta_map.end()) { 00392 // We have not encountered this pointer before. 00393 int pta_id = _next_pta_id; 00394 _next_pta_id++; 00395 00396 bool inserted = _pta_map.insert(PTAMap::value_type(ptr, pta_id)).second; 00397 nassertr(inserted, false); 00398 00399 write_pta_id(packet, pta_id); 00400 00401 // Return false to indicate the caller must now write out the 00402 // array definition. 00403 return false; 00404 00405 } else { 00406 // We have encountered this pointer before. 00407 int pta_id = (*pi).second; 00408 write_pta_id(packet, pta_id); 00409 00410 // Return true to indicate the caller need do nothing further. 00411 return true; 00412 } 00413 } 00414 00415 //////////////////////////////////////////////////////////////////// 00416 // Function: BamWriter::write_handle 00417 // Access: Public 00418 // Description: Writes a TypeHandle to the file in such a way that 00419 // the BamReader can read the same TypeHandle later via 00420 // read_handle(). 00421 //////////////////////////////////////////////////////////////////// 00422 void BamWriter:: 00423 write_handle(Datagram &packet, TypeHandle type) { 00424 // We encode TypeHandles within the Bam file by writing a unique 00425 // index number for each one to the file. When we write a 00426 // particular TypeHandle for the first time, we assign it a new 00427 // index number and then immediately follow it by its definition; 00428 // when we write the same TypeHandle on subsequent times we only 00429 // write the index number. 00430 00431 // The unique number we choose is actually the internal index number 00432 // of the TypeHandle. Why not? 00433 int index = type.get_index(); 00434 00435 // Also make sure the index number fits within a PN_uint16. 00436 nassertv(index <= 0xffff); 00437 00438 packet.add_uint16(index); 00439 00440 if (index != 0) { 00441 bool inserted = _types_written.insert(index).second; 00442 00443 if (inserted) { 00444 // This is the first time this TypeHandle has been written, so 00445 // also write out its definition. 00446 packet.add_string(type.get_name()); 00447 00448 // We also need to write the derivation of the TypeHandle, in case 00449 // the program reading this file later has never heard of this 00450 // type before. 00451 int num_parent_classes = type.get_num_parent_classes(); 00452 nassertv(num_parent_classes <= 255); // Good grief! 00453 packet.add_uint8(num_parent_classes); 00454 for (int i = 0; i < num_parent_classes; i++) { 00455 write_handle(packet, type.get_parent_class(i)); 00456 } 00457 } 00458 } 00459 } 00460 00461 00462 //////////////////////////////////////////////////////////////////// 00463 // Function: BamWriter::object_destructs 00464 // Access: Private 00465 // Description: This is called by the TypedWritable destructor. It 00466 // should remove the pointer from any structures that 00467 // keep a reference to it, and also write a flag to the 00468 // bam file (if it is open) so that a reader will know 00469 // the object id will no longer be used. 00470 //////////////////////////////////////////////////////////////////// 00471 void BamWriter:: 00472 object_destructs(TypedWritable *object) { 00473 StateMap::iterator si = _state_map.find(object); 00474 if (si != _state_map.end()) { 00475 // We ought to have written out the object by the time it 00476 // destructs, or we're in trouble when we do write it out. 00477 nassertv(!(*si).second._written_seq.is_initial()); 00478 00479 int object_id = (*si).second._object_id; 00480 _freed_object_ids.push_back(object_id); 00481 00482 _state_map.erase(si); 00483 } 00484 } 00485 00486 //////////////////////////////////////////////////////////////////// 00487 // Function: BamWriter::write_object_id 00488 // Access: Private 00489 // Description: Writes the indicated object id to the datagram. 00490 //////////////////////////////////////////////////////////////////// 00491 void BamWriter:: 00492 write_object_id(Datagram &dg, int object_id) { 00493 if (_long_object_id) { 00494 dg.add_uint32(object_id); 00495 00496 } else { 00497 dg.add_uint16(object_id); 00498 // Once we fill up our uint16, we write all object id's 00499 // thereafter with a uint32. 00500 if (object_id == 0xffff) { 00501 _long_object_id = true; 00502 } 00503 } 00504 } 00505 00506 //////////////////////////////////////////////////////////////////// 00507 // Function: BamWriter::write_pta_id 00508 // Access: Private 00509 // Description: Writes the indicated pta id to the datagram. 00510 //////////////////////////////////////////////////////////////////// 00511 void BamWriter:: 00512 write_pta_id(Datagram &dg, int pta_id) { 00513 if (_long_pta_id) { 00514 dg.add_uint32(pta_id); 00515 00516 } else { 00517 dg.add_uint16(pta_id); 00518 // Once we fill up our uint16, we write all pta id's 00519 // thereafter with a uint32. 00520 if (pta_id == 0xffff) { 00521 _long_pta_id = true; 00522 } 00523 } 00524 } 00525 00526 //////////////////////////////////////////////////////////////////// 00527 // Function: BamWriter::enqueue_object 00528 // Access: Private 00529 // Description: Assigns an object ID to the object and queues it up 00530 // for later writing to the Bam file. 00531 // 00532 // The return value is the object ID, or 0 if there is 00533 // an error. 00534 //////////////////////////////////////////////////////////////////// 00535 int BamWriter:: 00536 enqueue_object(const TypedWritable *object) { 00537 Datagram dg; 00538 00539 nassertr(object != TypedWritable::Null, 0); 00540 00541 // No object should ever be written out that is not registered as a 00542 // child of TypedWritable. The only way this can happen is if 00543 // someone failed to initialize their type correctly in init_type(). 00544 #ifndef NDEBUG 00545 if (!object->is_of_type(TypedWritable::get_class_type())) { 00546 util_cat.error() 00547 << "Type " << object->get_type() 00548 << " does not indicate inheritance from TypedWritable.\n" 00549 << "(this is almost certainly an oversight in " << object->get_type() 00550 << "::init_type().)\n"; 00551 } 00552 #endif 00553 00554 // We need to assign a unique index number to every object we write 00555 // out. Has this object been assigned a number yet? 00556 int object_id; 00557 00558 StateMap::iterator si = _state_map.find(object); 00559 if (si == _state_map.end()) { 00560 // No, it hasn't, so assign it the next number in sequence 00561 // arbitrarily. 00562 object_id = _next_object_id; 00563 00564 bool inserted = 00565 _state_map.insert(StateMap::value_type(object, StoreState(_next_object_id))).second; 00566 nassertr(inserted, false); 00567 { 00568 LightMutexHolder holder(TypedWritable::_bam_writers_lock); 00569 if (object->_bam_writers == ((TypedWritable::BamWriters *)NULL)) { 00570 ((TypedWritable *)object)->_bam_writers = new TypedWritable::BamWriters; 00571 } 00572 object->_bam_writers->push_back(this); 00573 } 00574 _next_object_id++; 00575 00576 } else { 00577 // Yes, it has; get the object ID. 00578 object_id = (*si).second._object_id; 00579 } 00580 00581 _object_queue.push_back(object); 00582 return object_id; 00583 } 00584 00585 //////////////////////////////////////////////////////////////////// 00586 // Function: BamWriter::flush_queue 00587 // Access: Private 00588 // Description: Writes all of the objects on the _object_queue to the 00589 // bam stream, until the queue is empty. 00590 // 00591 // Returns true on success, false on failure. 00592 //////////////////////////////////////////////////////////////////// 00593 bool BamWriter:: 00594 flush_queue() { 00595 nassertr(_target != NULL, false); 00596 // Each object we write may append more to the queue. 00597 while (!_object_queue.empty()) { 00598 const TypedWritable *object = _object_queue.front(); 00599 _object_queue.pop_front(); 00600 00601 // Look up the object in the map. It had better be there! 00602 StateMap::iterator si = _state_map.find(object); 00603 nassertr(si != _state_map.end(), false); 00604 00605 if ((*si).second._written_seq == _writing_seq) { 00606 // We have already visited this object; no need to consider it again. 00607 continue; 00608 } 00609 00610 int object_id = (*si).second._object_id; 00611 bool already_written = !(*si).second._written_seq.is_initial(); 00612 if ((*si).second._modified != object->get_bam_modified()) { 00613 // This object was previously written, but it has since been 00614 // modified, so we should write it again. 00615 already_written = false; 00616 } 00617 00618 Datagram dg; 00619 dg.add_uint8(_next_boc); 00620 _next_boc = BOC_adjunct; 00621 00622 if (!already_written) { 00623 // The first time we write a particular object, or when we 00624 // update the same object later, we do so by writing its 00625 // TypeHandle (which had better not be TypeHandle::none(), since 00626 // that's our code for a previously-written object), followed by 00627 // the object ID number, followed by the object definition. 00628 00629 TypeHandle type = object->get_type(); 00630 nassertr(type != TypeHandle::none(), false); 00631 00632 // Determine what the nearest kind of type is that the reader 00633 // will be able to handle, and write that instead. 00634 TypeHandle registered_type = 00635 BamReader::get_factory()->find_registered_type(type); 00636 if (registered_type == TypeHandle::none()) { 00637 // We won't be able to read this type again. 00638 util_cat.warning() 00639 << "Objects of type " << type << " cannot be read; bam file is invalid.\n"; 00640 } else if (registered_type != type) { 00641 util_cat.info() 00642 << "Writing " << registered_type << " instead of " << type << "\n"; 00643 type = registered_type; 00644 00645 } else if (util_cat.is_debug()) { 00646 util_cat.debug() 00647 << "Writing " << type << " object id " << object_id 00648 << " to bam file\n"; 00649 } 00650 00651 write_handle(dg, type); 00652 write_object_id(dg, object_id); 00653 00654 // We cast the const pointer to non-const so that we may call 00655 // write_datagram() on it. Really, write_datagram() should be a 00656 // const method anyway, but there may be times when a class 00657 // object wants to update some transparent cache value during 00658 // writing or something like that, so it's more convenient to 00659 // cheat and define it as a non-const method. 00660 ((TypedWritable *)object)->write_datagram(this, dg); 00661 00662 (*si).second._written_seq = _writing_seq; 00663 (*si).second._modified = object->get_bam_modified(); 00664 00665 } else { 00666 // On subsequent times when we write a particular object, we 00667 // write simply TypeHandle::none(), followed by the object ID. 00668 // The occurrence of TypeHandle::none() is an indicator to the 00669 // BamReader that this is a previously-written object. 00670 00671 write_handle(dg, TypeHandle::none()); 00672 write_object_id(dg, object_id); 00673 00674 // The object has not been modified, but maybe one of its child 00675 // objects has. 00676 ((TypedWritable *)object)->update_bam_nested(this); 00677 } 00678 00679 if (!_target->put_datagram(dg)) { 00680 util_cat.error() 00681 << "Unable to write data to output.\n"; 00682 return false; 00683 } 00684 } 00685 00686 return true; 00687 }