diff options
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs')
-rw-r--r-- | OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs | 389 |
1 files changed, 290 insertions, 99 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs index cfb082b..1e9cfba 100644 --- a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs | |||
@@ -188,27 +188,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory | |||
188 | // Process the visual params, this may change height as well | 188 | // Process the visual params, this may change height as well |
189 | if (visualParams != null) | 189 | if (visualParams != null) |
190 | { | 190 | { |
191 | // string[] visualParamsStrings = new string[visualParams.Length]; | ||
192 | // for (int i = 0; i < visualParams.Length; i++) | ||
193 | // visualParamsStrings[i] = visualParams[i].ToString(); | ||
194 | // m_log.DebugFormat( | ||
195 | // "[AVFACTORY]: Setting visual params for {0} to {1}", | ||
196 | // client.Name, string.Join(", ", visualParamsStrings)); | ||
197 | /* | ||
198 | float oldHeight = sp.Appearance.AvatarHeight; | ||
199 | changed = sp.Appearance.SetVisualParams(visualParams); | ||
200 | |||
201 | if (sp.Appearance.AvatarHeight != oldHeight && sp.Appearance.AvatarHeight > 0) | ||
202 | ((ScenePresence)sp).SetHeight(sp.Appearance.AvatarHeight); | ||
203 | */ | ||
204 | // float oldoff = sp.Appearance.AvatarFeetOffset; | ||
205 | // Vector3 oldbox = sp.Appearance.AvatarBoxSize; | ||
206 | changed = sp.Appearance.SetVisualParams(visualParams); | 191 | changed = sp.Appearance.SetVisualParams(visualParams); |
207 | // float off = sp.Appearance.AvatarFeetOffset; | ||
208 | // Vector3 box = sp.Appearance.AvatarBoxSize; | ||
209 | // if(oldoff != off || oldbox != box) | ||
210 | // ((ScenePresence)sp).SetSize(box,off); | ||
211 | |||
212 | } | 192 | } |
213 | 193 | ||
214 | // Process the baked texture array | 194 | // Process the baked texture array |
@@ -222,9 +202,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory | |||
222 | 202 | ||
223 | // WriteBakedTexturesReport(sp, m_log.DebugFormat); | 203 | // WriteBakedTexturesReport(sp, m_log.DebugFormat); |
224 | 204 | ||
225 | // If bake textures are missing and this is not an NPC, request a rebake from client | 205 | UpdateBakedTextureCache(sp, cacheItems); |
226 | if (!ValidateBakedTextureCache(sp) && (((ScenePresence)sp).PresenceType != PresenceType.Npc)) | ||
227 | RequestRebake(sp, true); | ||
228 | 206 | ||
229 | // This appears to be set only in the final stage of the appearance | 207 | // This appears to be set only in the final stage of the appearance |
230 | // update transaction. In theory, we should be able to do an immediate | 208 | // update transaction. In theory, we should be able to do an immediate |
@@ -377,114 +355,335 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory | |||
377 | } | 355 | } |
378 | } | 356 | } |
379 | 357 | ||
380 | public bool ValidateBakedTextureCache(IScenePresence sp) | 358 | // called on textures update |
359 | public bool UpdateBakedTextureCache(IScenePresence sp, WearableCacheItem[] cacheItems) | ||
381 | { | 360 | { |
382 | bool defonly = true; // are we only using default textures | 361 | // npcs dont have baked cache |
383 | IImprovedAssetCache cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); | 362 | if (((ScenePresence)sp).isNPC) |
384 | IBakedTextureModule bakedModule = m_scene.RequestModuleInterface<IBakedTextureModule>(); | 363 | return true; |
385 | WearableCacheItem[] wearableCache = null; | 364 | |
386 | 365 | // uploaded baked textures will be in assets local cache | |
387 | // Cache wearable data for teleport. | 366 | IAssetService cache = m_scene.AssetService; |
388 | // Only makes sense if there's a bake module and a cache module | 367 | IBakedTextureModule m_BakedTextureModule = m_scene.RequestModuleInterface<IBakedTextureModule>(); |
389 | if (bakedModule != null && cache != null) | 368 | |
369 | int validDirtyBakes = 0; | ||
370 | int hits = 0; | ||
371 | |||
372 | // our main cacheIDs mapper is p.Appearance.WearableCacheItems | ||
373 | WearableCacheItem[] wearableCache = sp.Appearance.WearableCacheItems; | ||
374 | |||
375 | if (wearableCache == null) | ||
390 | { | 376 | { |
391 | try | 377 | wearableCache = WearableCacheItem.GetDefaultCacheItem(); |
392 | { | 378 | } |
393 | wearableCache = bakedModule.Get(sp.UUID); | 379 | |
394 | } | 380 | List<UUID> missing = new List<UUID>(); |
395 | catch (Exception) | 381 | |
396 | { | 382 | // Process received baked textures |
383 | for (int i = 0; i < cacheItems.Length; i++) | ||
384 | { | ||
385 | int idx = (int)cacheItems[i].TextureIndex; | ||
386 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; | ||
397 | 387 | ||
388 | // No face | ||
389 | if (face == null) | ||
390 | { | ||
391 | // for some reason viewer is cleaning this | ||
392 | sp.Appearance.Texture.FaceTextures[idx] = sp.Appearance.Texture.CreateFace((uint) idx); | ||
393 | sp.Appearance.Texture.FaceTextures[idx].TextureID = AppearanceManager.DEFAULT_AVATAR_TEXTURE; | ||
394 | wearableCache[idx].CacheId = UUID.Zero; | ||
395 | wearableCache[idx].TextureID = UUID.Zero; | ||
396 | wearableCache[idx].TextureAsset = null; | ||
397 | continue; | ||
398 | } | 398 | } |
399 | if (wearableCache != null) | 399 | else |
400 | { | 400 | { |
401 | for (int i = 0; i < wearableCache.Length; i++) | 401 | if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE) |
402 | { | 402 | { |
403 | cache.Cache(wearableCache[i].TextureAsset); | 403 | wearableCache[idx].CacheId = UUID.Zero; |
404 | wearableCache[idx].TextureID = UUID.Zero; | ||
405 | wearableCache[idx].TextureAsset = null; | ||
406 | continue; | ||
404 | } | 407 | } |
405 | } | 408 | |
406 | } | 409 | /* |
407 | /* | 410 | if (face.TextureID == wearableCache[idx].TextureID && m_BakedTextureModule != null) |
408 | IBakedTextureModule bakedModule = m_scene.RequestModuleInterface<IBakedTextureModule>(); | ||
409 | if (invService.GetRootFolder(userID) != null) | ||
410 | { | ||
411 | WearableCacheItem[] wearableCache = null; | ||
412 | if (bakedModule != null) | ||
413 | { | ||
414 | try | ||
415 | { | 411 | { |
416 | wearableCache = bakedModule.Get(userID); | 412 | if (wearableCache[idx].CacheId != cacheItems[i].CacheId) |
417 | appearance.WearableCacheItems = wearableCache; | ||
418 | appearance.WearableCacheItemsDirty = false; | ||
419 | foreach (WearableCacheItem item in wearableCache) | ||
420 | { | 413 | { |
421 | appearance.Texture.FaceTextures[item.TextureIndex].TextureID = item.TextureID; | 414 | wearableCache[idx].CacheId = cacheItems[i].CacheId; |
415 | validDirtyBakes++; | ||
416 | |||
417 | //assuming this can only happen if asset is in cache | ||
422 | } | 418 | } |
419 | hits++; | ||
420 | continue; | ||
421 | } | ||
422 | */ | ||
423 | wearableCache[idx].TextureAsset = null; | ||
424 | if (cache != null) | ||
425 | wearableCache[idx].TextureAsset = cache.GetCached(face.TextureID.ToString()); | ||
426 | |||
427 | if (wearableCache[idx].TextureAsset != null) | ||
428 | { | ||
429 | if ( wearableCache[idx].TextureID != face.TextureID || | ||
430 | wearableCache[idx].CacheId != cacheItems[i].CacheId) | ||
431 | validDirtyBakes++; | ||
432 | |||
433 | wearableCache[idx].TextureID = face.TextureID; | ||
434 | wearableCache[idx].CacheId = cacheItems[i].CacheId; | ||
435 | hits++; | ||
423 | } | 436 | } |
424 | catch (Exception) | 437 | else |
425 | { | 438 | { |
426 | 439 | wearableCache[idx].CacheId = UUID.Zero; | |
440 | wearableCache[idx].TextureID = UUID.Zero; | ||
441 | wearableCache[idx].TextureAsset = null; | ||
442 | missing.Add(face.TextureID); | ||
443 | continue; | ||
427 | } | 444 | } |
428 | } | 445 | } |
429 | */ | 446 | } |
430 | 447 | ||
431 | // Process the texture entry | 448 | sp.Appearance.WearableCacheItems = wearableCache; |
432 | for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) | 449 | |
450 | if (missing.Count > 0) | ||
433 | { | 451 | { |
434 | int idx = AvatarAppearance.BAKE_INDICES[i]; | 452 | foreach (UUID id in missing) |
435 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; | 453 | sp.ControllingClient.SendRebakeAvatarTextures(id); |
454 | } | ||
436 | 455 | ||
437 | // No face, so lets check our baked service cache, teleport or login. | 456 | if (validDirtyBakes > 0 && hits == cacheItems.Length) |
438 | if (face == null) | 457 | { |
458 | // if we got a full set of baked textures save all in BakedTextureModule | ||
459 | if (m_BakedTextureModule != null) | ||
439 | { | 460 | { |
440 | if (wearableCache != null) | 461 | m_log.Debug("[UpdateBakedCache] uploading to bakedModule cache"); |
462 | |||
463 | m_BakedTextureModule.Store(sp.UUID); | ||
464 | } | ||
465 | } | ||
466 | |||
467 | |||
468 | // debug | ||
469 | m_log.Debug("[UpdateBakedCache] cache hits: " + hits.ToString() + " changed entries: " + validDirtyBakes.ToString() + " rebakes " + missing.Count); | ||
470 | /* | ||
471 | for (int iter = 0; iter < AvatarAppearance.BAKE_INDICES.Length; iter++) | ||
472 | { | ||
473 | int j = AvatarAppearance.BAKE_INDICES[iter]; | ||
474 | m_log.Debug("[UpdateBCache] {" + iter + "/" + | ||
475 | sp.Appearance.WearableCacheItems[j].TextureIndex + "}: c-" + | ||
476 | sp.Appearance.WearableCacheItems[j].CacheId + ", t-" + | ||
477 | sp.Appearance.WearableCacheItems[j].TextureID); | ||
478 | } | ||
479 | */ | ||
480 | return (hits == cacheItems.Length); | ||
481 | } | ||
482 | |||
483 | // called when we get a new root avatar | ||
484 | public bool ValidateBakedTextureCache(IScenePresence sp) | ||
485 | { | ||
486 | int hits = 0; | ||
487 | |||
488 | if (((ScenePresence)sp).isNPC) | ||
489 | return true; | ||
490 | |||
491 | lock (m_setAppearanceLock) | ||
492 | { | ||
493 | IAssetService cache = m_scene.AssetService; | ||
494 | IBakedTextureModule bakedModule = m_scene.RequestModuleInterface<IBakedTextureModule>(); | ||
495 | WearableCacheItem[] bakedModuleCache = null; | ||
496 | |||
497 | if (cache == null) | ||
498 | return false; | ||
499 | |||
500 | WearableCacheItem[] wearableCache = sp.Appearance.WearableCacheItems; | ||
501 | |||
502 | // big debug | ||
503 | m_log.DebugFormat("[AVFACTORY]: ValidateBakedTextureCache start for {0} {1}", sp.Name, sp.UUID); | ||
504 | /* | ||
505 | for (int iter = 0; iter < AvatarAppearance.BAKE_INDICES.Length; iter++) | ||
506 | { | ||
507 | int j = AvatarAppearance.BAKE_INDICES[iter]; | ||
508 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[j]; | ||
509 | if (wearableCache == null) | ||
441 | { | 510 | { |
442 | // If we find the an appearance item, set it as the textureentry and the face | 511 | if (face != null) |
443 | WearableCacheItem searchitem = WearableCacheItem.SearchTextureIndex((uint) idx, wearableCache); | 512 | m_log.Debug("[ValidateBakedCache] {" + iter + "/" + j + " t- " + face.TextureID); |
444 | if (searchitem != null) | ||
445 | { | ||
446 | sp.Appearance.Texture.FaceTextures[idx] = sp.Appearance.Texture.CreateFace((uint) idx); | ||
447 | sp.Appearance.Texture.FaceTextures[idx].TextureID = searchitem.TextureID; | ||
448 | face = sp.Appearance.Texture.FaceTextures[idx]; | ||
449 | } | ||
450 | else | 513 | else |
451 | { | 514 | m_log.Debug("[ValidateBakedCache] {" + iter + "/" + j + " t- No texture"); |
452 | // if there is no texture entry and no baked cache, skip it | ||
453 | continue; | ||
454 | } | ||
455 | } | 515 | } |
456 | else | 516 | else |
457 | { | 517 | { |
458 | //No texture entry face and no cache. Skip this face. | 518 | if (face != null) |
459 | continue; | 519 | m_log.Debug("[ValidateBakedCache] {" + iter + "/" + j + " ft- " + face.TextureID + |
520 | "}: cc-" + | ||
521 | wearableCache[j].CacheId + ", ct-" + | ||
522 | wearableCache[j].TextureID | ||
523 | ); | ||
524 | else | ||
525 | m_log.Debug("[ValidateBakedCache] {" + iter + "/" + j + " t - No texture" + | ||
526 | "}: cc-" + | ||
527 | wearableCache[j].CacheId + ", ct-" + | ||
528 | wearableCache[j].TextureID | ||
529 | ); | ||
460 | } | 530 | } |
461 | } | 531 | } |
532 | <<<<<<< HEAD | ||
462 | 533 | ||
463 | // m_log.DebugFormat( | 534 | // m_log.DebugFormat( |
464 | // "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}", | 535 | // "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}", |
465 | // face.TextureID, idx, client.Name, client.AgentId); | 536 | // face.TextureID, idx, client.Name, client.AgentId); |
537 | ======= | ||
538 | */ | ||
539 | bool wearableCacheValid = false; | ||
540 | if (wearableCache == null) | ||
541 | { | ||
542 | wearableCache = WearableCacheItem.GetDefaultCacheItem(); | ||
543 | } | ||
544 | else | ||
545 | { | ||
546 | // we may have received a full cache | ||
547 | // check same coerence and store | ||
548 | wearableCacheValid = true; | ||
549 | for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) | ||
550 | { | ||
551 | int idx = AvatarAppearance.BAKE_INDICES[i]; | ||
552 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; | ||
553 | if (face != null) | ||
554 | { | ||
555 | if (face.TextureID == wearableCache[idx].TextureID && | ||
556 | face.TextureID != UUID.Zero) | ||
557 | { | ||
558 | if (wearableCache[idx].TextureAsset != null) | ||
559 | { | ||
560 | hits++; | ||
561 | wearableCache[idx].TextureAsset.Temporary = true; | ||
562 | wearableCache[idx].TextureAsset.Local = true; | ||
563 | cache.Store(wearableCache[idx].TextureAsset); | ||
564 | continue; | ||
565 | } | ||
566 | if (cache.GetCached((wearableCache[idx].TextureID).ToString()) != null) | ||
567 | { | ||
568 | hits++; | ||
569 | continue; | ||
570 | } | ||
571 | } | ||
572 | wearableCacheValid = false; | ||
573 | } | ||
574 | } | ||
575 | |||
576 | wearableCacheValid = (wearableCacheValid && (hits >= AvatarAppearance.BAKE_INDICES.Length - 1)); | ||
577 | if (wearableCacheValid) | ||
578 | m_log.Debug("[ValidateBakedCache] have valid local cache"); | ||
579 | } | ||
580 | >>>>>>> avn/ubitvar | ||
466 | 581 | ||
467 | // if the texture is one of the "defaults" then skip it | 582 | bool checkExternal = false; |
468 | // this should probably be more intelligent (skirt texture doesnt matter | ||
469 | // if the avatar isnt wearing a skirt) but if any of the main baked | ||
470 | // textures is default then the rest should be as well | ||
471 | if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE) | ||
472 | continue; | ||
473 | |||
474 | defonly = false; // found a non-default texture reference | ||
475 | 583 | ||
584 | <<<<<<< HEAD | ||
476 | if (m_scene.AssetService.Get(face.TextureID.ToString()) == null) | 585 | if (m_scene.AssetService.Get(face.TextureID.ToString()) == null) |
477 | return false; | 586 | return false; |
478 | } | 587 | } |
588 | ======= | ||
589 | if (!wearableCacheValid) | ||
590 | { | ||
591 | // only use external bake module on login condition check | ||
592 | // ScenePresence ssp = null; | ||
593 | // if (sp is ScenePresence) | ||
594 | { | ||
595 | // ssp = (ScenePresence)sp; | ||
596 | // checkExternal = (((uint)ssp.TeleportFlags & (uint)TeleportFlags.ViaLogin) != 0) && | ||
597 | // bakedModule != null; | ||
598 | |||
599 | // or do it anytime we dont have the cache | ||
600 | checkExternal = bakedModule != null; | ||
601 | } | ||
602 | } | ||
479 | 603 | ||
480 | // m_log.DebugFormat("[AVFACTORY]: Completed texture check for {0} {1}", sp.Name, sp.UUID); | 604 | if (checkExternal) |
605 | { | ||
606 | hits = 0; | ||
607 | bool gotbacked = false; | ||
481 | 608 | ||
482 | // If we only found default textures, then the appearance is not cached | 609 | m_log.Debug("[ValidateBakedCache] local cache invalid, checking bakedModule"); |
483 | return (defonly ? false : true); | 610 | try |
611 | { | ||
612 | bakedModuleCache = bakedModule.Get(sp.UUID); | ||
613 | } | ||
614 | catch (Exception e) | ||
615 | { | ||
616 | m_log.ErrorFormat(e.ToString()); | ||
617 | bakedModuleCache = null; | ||
618 | } | ||
619 | |||
620 | if (bakedModuleCache != null) | ||
621 | { | ||
622 | m_log.Debug("[ValidateBakedCache] got bakedModule " + bakedModuleCache.Length + " cached textures"); | ||
623 | |||
624 | for (int i = 0; i < bakedModuleCache.Length; i++) | ||
625 | { | ||
626 | int j = (int)bakedModuleCache[i].TextureIndex; | ||
627 | |||
628 | if (bakedModuleCache[i].TextureAsset != null) | ||
629 | { | ||
630 | wearableCache[j].TextureID = bakedModuleCache[i].TextureID; | ||
631 | wearableCache[j].CacheId = bakedModuleCache[i].CacheId; | ||
632 | wearableCache[j].TextureAsset = bakedModuleCache[i].TextureAsset; | ||
633 | bakedModuleCache[i].TextureAsset.Temporary = true; | ||
634 | bakedModuleCache[i].TextureAsset.Local = true; | ||
635 | cache.Store(bakedModuleCache[i].TextureAsset); | ||
636 | } | ||
637 | } | ||
638 | gotbacked = true; | ||
639 | } | ||
640 | |||
641 | if (gotbacked) | ||
642 | { | ||
643 | // force the ones we got | ||
644 | for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) | ||
645 | { | ||
646 | int idx = AvatarAppearance.BAKE_INDICES[i]; | ||
647 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; | ||
648 | |||
649 | if (sp.Appearance.Texture.FaceTextures[idx] == null) | ||
650 | sp.Appearance.Texture.FaceTextures[idx] = sp.Appearance.Texture.CreateFace((uint)idx); | ||
651 | sp.Appearance.Texture.FaceTextures[idx].TextureID = wearableCache[idx].TextureID; | ||
652 | face = sp.Appearance.Texture.FaceTextures[idx]; | ||
653 | |||
654 | // this should be removed | ||
655 | if (face.TextureID != UUID.Zero && face.TextureID != AppearanceManager.DEFAULT_AVATAR_TEXTURE) | ||
656 | hits++; | ||
657 | continue; | ||
658 | } | ||
659 | } | ||
660 | } | ||
661 | >>>>>>> avn/ubitvar | ||
662 | |||
663 | sp.Appearance.WearableCacheItems = wearableCache; | ||
664 | |||
665 | } | ||
666 | |||
667 | // debug | ||
668 | m_log.DebugFormat("[ValidateBakedCache]: Completed texture check for {0} {1} with {2} hits", sp.Name, sp.UUID, hits); | ||
669 | /* | ||
670 | for (int iter = 0; iter < AvatarAppearance.BAKE_INDICES.Length; iter++) | ||
671 | { | ||
672 | int j = AvatarAppearance.BAKE_INDICES[iter]; | ||
673 | m_log.Debug("[ValidateBakedCache] {" + iter + "/" + | ||
674 | sp.Appearance.WearableCacheItems[j].TextureIndex + "}: c-" + | ||
675 | sp.Appearance.WearableCacheItems[j].CacheId + ", t-" + | ||
676 | sp.Appearance.WearableCacheItems[j].TextureID); | ||
677 | } | ||
678 | */ | ||
679 | return (hits >= AvatarAppearance.BAKE_INDICES.Length - 1); // skirt is optional | ||
484 | } | 680 | } |
485 | 681 | ||
486 | public int RequestRebake(IScenePresence sp, bool missingTexturesOnly) | 682 | public int RequestRebake(IScenePresence sp, bool missingTexturesOnly) |
487 | { | 683 | { |
684 | if (((ScenePresence)sp).isNPC) | ||
685 | return 0; | ||
686 | |||
488 | int texturesRebaked = 0; | 687 | int texturesRebaked = 0; |
489 | // IImprovedAssetCache cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); | 688 | // IImprovedAssetCache cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); |
490 | 689 | ||
@@ -497,14 +696,6 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory | |||
497 | if (face == null) | 696 | if (face == null) |
498 | continue; | 697 | continue; |
499 | 698 | ||
500 | // m_log.DebugFormat( | ||
501 | // "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}", | ||
502 | // face.TextureID, idx, client.Name, client.AgentId); | ||
503 | |||
504 | // if the texture is one of the "defaults" then skip it | ||
505 | // this should probably be more intelligent (skirt texture doesnt matter | ||
506 | // if the avatar isnt wearing a skirt) but if any of the main baked | ||
507 | // textures is default then the rest should be as well | ||
508 | if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE) | 699 | if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE) |
509 | continue; | 700 | continue; |
510 | 701 | ||