// Worker threads come here to take items off the work queue.
 // Multiple threads loop around here taking items off the work queue and
 //   processing them. When there are no more things to do, the threads
 //   return which puts them back in the thread pool.
 private void DoWork(object x)
 {
     while (m_workItems.Count > 0)
     {
         DoLaterBase w = null;
         lock (m_workItems) {
             if (m_workItems.Count > 0)
             {
                 w = m_workItems.Dequeue();
             }
         }
         if (w != null)
         {
             try {
                 if (!w.DoIt())
                 {
                     // LogManager.Log.Log(LogLevel.DRENDERDETAIL, "{0}.DoLater: DoWork: DoEvenLater", m_queueName);
                     DoItEvenLater(w);
                 }
             }
             catch (Exception e) {
                 LogManager.Log.Log(LogLevel.DBADERROR, "{0}.DoLater: DoWork: EXCEPTION: {1}",
                                    m_queueName, e);
                 // we drop the work item in  the belief that it will exception again next time
             }
         }
     }
     lock (m_workItems) {
         m_activeWorkProcessors--; // not sure if this is atomic
     }
 }
 // IWorkQueue.DoLater()
 public void DoLater(DoLaterBase w)
 {
     w.containingClass = this;
     w.remainingWait   = 0; // the first time through, do it now
     w.timesRequeued   = 0;
     AddWorkItemToQueue(w);
 }
        // requeuing the work item. Since requeuing, add the delay
        public void DoLaterRequeue(ref DoLaterBase w)
        {
            w.timesRequeued++;
            int nextTime = Math.Min(w.requeueWait * w.timesRequeued, 5000);

            w.remainingWait = System.Environment.TickCount + nextTime;
            AddToWorkQueue(w);
        }
 private void DoItEvenLater(DoLaterBase w)
 {
     w.timesRequeued++;
     lock (m_doEvenLater) {
         int nextTime = Math.Min(w.requeueWait * w.timesRequeued, 2000);
         nextTime        = Math.Max(nextTime, 100); // never less than this
         w.remainingWait = Utilities.TickCount() + nextTime;
         m_doEvenLater.Add(w);
     }
 }
 public void DoLater(DoLaterBase w)
 {
     if (((m_totalRequests++) % 100) == 0)
     {
         LogManager.Log.Log(LogLevel.DRENDERDETAIL, "{0}.DoLater: Queuing. requests={1}, queueSize={2}",
                            m_queueName, m_totalRequests, m_workQueue.Count);
     }
     w.containingClass = this;
     w.remainingWait   = 0; // the first time through, do it now
     w.timesRequeued   = 0;
     AddToWorkQueue(w);
 }
        // A thread from the outside world calls in here to do some work on the queue
        // We process work items on the queue until the queue is empty or we reach 'maximumCost'.
        // Each queued item has a delay (a time in the future when it can be done) and a
        // cost. As the work items are done, the cost is added up.
        // This means the thread coming in can count on being here only a limited amount
        // of time.
        public void ProcessQueue(int maximumCost)
        {
            int         totalCost    = 0;
            int         totalCounter = 100;
            int         now          = System.Environment.TickCount;
            DoLaterBase found        = null;

            while ((totalCost < maximumCost) && (totalCounter > 0) && (m_workQueue.Count > 0))
            {
                now   = System.Environment.TickCount;
                found = null;
                lock (m_workQueue) {
                    // find an entry in the list who's time has come
                    foreach (DoLaterBase ww in m_workQueue)
                    {
                        if (ww.remainingWait < now)
                        {
                            found = ww;
                            break;
                        }
                    }
                    if (found != null)
                    {
                        // if found, remove from list
                        m_workQueue.Remove(found);
                    }
                }
                if (found == null)
                {
                    // if nothing found, we're done
                    break;
                }
                else
                {
                    // try to do the operation
                    totalCounter--;
                    if (found.DoIt())
                    {
                        // if it worked, count it as successful
                        totalCost += found.cost;
                    }
                    else
                    {
                        // if it didn't work, requeue it for later
                        ((OnDemandWorkQueue)found.containingClass).DoLaterRequeue(ref found);
                    }
                }
            }
        }
 // Add a new work item to the work queue. If we don't have the maximum number
 // of worker threads already working on the queue, start a new thread from
 // the pool to empty the queue.
 private void AddWorkItemToQueue(DoLaterBase w)
 {
     if ((m_totalRequests++ % 100) == 0)
     {
         LogManager.Log.Log(LogLevel.DRENDERDETAIL, "{0}.DoLater: Queuing, c={1}, l={2}",
                            m_queueName, m_totalRequests, m_workItems.Count);
     }
     lock (m_workItems) {
         m_workItems.Enqueue(w);
         if (m_activeWorkProcessors < m_workProcessorsMax)
         {
             m_activeWorkProcessors++;
             ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoWork), null);
             // ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.DoWork), null);
         }
     }
 }
 /// <summary>
 /// Add the work item to the queue in the order order
 /// </summary>
 /// <param name="w"></param>
 private void AddToWorkQueue(DoLaterBase w)
 {
     lock (m_workQueue) {
         /*
          * // Experimental code trying to give some order to the requests
          * LinkedListNode<DoLaterBase> foundItem = null;
          * for (LinkedListNode<DoLaterBase> ii = m_workQueue.First; ii != null; ii = ii.Next) {
          *  if (w.order < ii.Value.order) {
          *      foundItem = ii;
          *      break;
          *  }
          * }
          * if (foundItem != null) {
          *  // we're pointing to an element to put our element before
          *  m_workQueue.AddBefore(foundItem, w);
          * }
          * else {
          *  // just put it on the end
          *  m_workQueue.AddLast(w);
          * }
          */
         m_workQueue.AddLast(w);
     }
 }
 private void DoItEvenLater(DoLaterBase w)
 {
     w.timesRequeued++;
     lock (m_doEvenLater) {
     int nextTime = Math.Min(w.requeueWait * w.timesRequeued, 2000);
     nextTime = Math.Max(nextTime, 100);     // never less than this
     w.remainingWait = Utilities.TickCount() + nextTime;
     m_doEvenLater.Add(w);
     }
 }
        /// <summary>
        /// On our own thread, make  the synchronous texture request from the asset server
        /// </summary>
        /// <param name="qInstance"></param>
        /// <param name="obinID"></param>
        /// <returns></returns>
        private bool ThrottleTextureMakeRequest(DoLaterBase qInstance, Object obinID)
        {
            OMV.UUID binID = (OMV.UUID)obinID;

            Uri assetPath = new Uri(m_basePath + "/assets/" + binID.ToString() + (m_dataFetch ? "/data" : ""));
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(assetPath);
            request.MaximumAutomaticRedirections = 4;
            request.MaximumResponseHeadersLength = 4;
            request.Timeout = 30000;    // 30 second timeout
            if (m_proxyPath != null) {
            // configure proxy if necessary
            WebProxy myProxy = new WebProxy();
            myProxy.Address = new Uri(m_proxyPath);
            request.Proxy = myProxy;
            }
            try {
            m_log.Log(LogLevel.DCOMMDETAIL, "ThrottleTextureMakeRequest: requesting '{0}'", assetPath);
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
                m_log.Log(LogLevel.DCOMMDETAIL, "ThrottleTextureMakeRequest: request returned. resp={0}, l={1}",
                            response.StatusCode, response.ContentLength);
                if (response.StatusCode == HttpStatusCode.OK) {
                    using (Stream receiveStream = response.GetResponseStream()) {
                        OMV.Assets.AssetTexture at;
                        if (m_dataFetch) {
                            // we're getting raw binary data
                            byte[] textureBuff = new byte[response.ContentLength];
                            receiveStream.Read(textureBuff, 0, (int)response.ContentLength);
                            at = new OMV.Assets.AssetTexture(binID, textureBuff);
                        }
                        else {
                            // receiving a serialized package
                            XmlSerializer xserial = new XmlSerializer(typeof(OpenSim.Framework.AssetBase));
                            OpenSim.Framework.AssetBase abase = (OpenSim.Framework.AssetBase)xserial.Deserialize(receiveStream);
                            at = new OMV.Assets.AssetTexture(binID, abase.Data);
                        }
                        ProcessDownloadFinished(OMV.TextureRequestState.Finished, at);
                    }
                }
                else {
                    OMV.Assets.AssetTexture at = new OMV.Assets.AssetTexture(binID, new byte[0]);
                    ProcessDownloadFinished(OMV.TextureRequestState.NotFound, at);
                }
            }
            }
            catch (Exception e) {
            m_log.Log(LogLevel.DBADERROR, "Error fetching asset: {0}", e);
            OMV.Assets.AssetTexture at = new OMV.Assets.AssetTexture(binID, new byte[0]);
            ProcessDownloadFinished(OMV.TextureRequestState.NotFound, at);
            }
            return true;
        }
        /// <summary>
        /// Current animations are for TargetOmega which causes rotation in the client.
        /// </summary>
        /// <param name="qInstance"></param>
        /// <param name="parms"></param>
        /// <returns>true if we can do this update now. False if to retry</returns>
        private bool DoUpdateAnimationLater(DoLaterBase qInstance, Object parms)
        {
            Object[] loadParams = (Object[])parms;
            float prio = (float)loadParams[0];
            IEntity m_ent = (IEntity)loadParams[1];
            IWorldRenderConv m_conver = (IWorldRenderConv)loadParams[2];
            IAnimation m_anim = (IAnimation)loadParams[3];

            string sceneNodeName = RendererOgre.GetSceneNodeName(m_ent);
            if (sceneNodeName == null) {
            // prim does not yet have a scene node. Try again later.
            return false;
            }
            return m_conver.UpdateAnimation(0, m_ent, sceneNodeName, m_anim);
        }
 private bool QueueTilLaterDoIt(DoLaterBase dlb, Object p)
 {
     Object[] parms = (Object[])p;
     CommActionCode cac = (CommActionCode)parms[1];
     // m_log.Log(LogLevel.DCOMMDETAIL, "QueueTilLaterDoIt: c={0}", cac);
     RegionAction(cac, parms[2], parms[3], parms[4], parms[5]);
     return true;
 }
Example #13
0
        private bool UI_MouseMoveLater(DoLaterBase qInstance, Object parms)
        {
            Object[] loadParams = (Object[])parms;
            int param = (int)loadParams[0];
            float x = (float)loadParams[1];
            float y = (float)loadParams[2];

            int sinceLastMouse = System.Environment.TickCount - m_lastMouseMoveTime;
            m_lastMouseMoveTime = System.Environment.TickCount;
            // m_log.Log(LogLevel.DVIEWDETAIL, "OnMouseMove: x={0}, y={1}, time since last={2}", x, y, sinceLastMouse);
            if (m_mainCamera != null) {
            if (((Renderer.UserInterface.LastKeyCode & Keys.Control) == 0)
                    && ((Renderer.UserInterface.LastKeyCode & Keys.Alt) != 0)) {
                m_log.Log(LogLevel.DVIEWDETAIL, "OnMouseMove: ALT: ");
            }
            else if ( ((Renderer.UserInterface.LastKeyCode & Keys.Control) != 0)
                    && ((Renderer.UserInterface.LastKeyCode & Keys.Alt) != 0) ) {
                // if ALT+CNTL is held down, movement is on view plain
                float xMove = x * m_cameraSpeed;
                float yMove = y * m_cameraSpeed;
                OMV.Vector3d movement = new OMV.Vector3d( 0, xMove, yMove);
                m_log.Log(LogLevel.DVIEWDETAIL, "OnMouseMove: CNTL-ALT: Move camera x={0}, y={1}", xMove, yMove);
                m_mainCamera.GlobalPosition -= movement;
            }
            else if ((Renderer.UserInterface.LastKeyCode & Keys.Control) != 0) {
                // if CNTL is held down, movement is on land plane
                float xMove = x * m_cameraSpeed;
                float yMove = y * m_cameraSpeed;
                m_log.Log(LogLevel.DVIEWDETAIL, "OnMouseMove: CNTL: Move camera x={0}, y={1}", xMove, yMove);
                OMV.Vector3d movement = new OMV.Vector3d( yMove, xMove, 0f);
                m_mainCamera.GlobalPosition -= movement;
            }
            else if ((Renderer.UserInterface.LastMouseButtons & MouseButtons.Left) != 0) {
                    // move the camera around the horizontal (X) and vertical (Z) axis
                    float xMove = (-x * m_cameraRotationSpeed * Constants.DEGREETORADIAN) % Constants.TWOPI;
                    float yMove = (-y * m_cameraRotationSpeed * Constants.DEGREETORADIAN) % Constants.TWOPI;
                    // rotate around local axis
                    // m_log.Log(LogLevel.DVIEWDETAIL, "OnMouseMove: Rotate camera x={0}, y={1}, lmb={2}",
                    //         xMove, yMove, Renderer.UserInterface.LastMouseButtons);
                    m_mainCamera.rotate(yMove, 0f, xMove);
            }
            }
            return true;
        }
 private bool ThrottleTextureMakeRequest(DoLaterBase qInstance, Object obinID)
 {
     OMV.UUID binID = (OMV.UUID)obinID;
     m_comm.GridClient.Assets.RequestImage(binID, OMV.ImageType.Normal, 1013000f, 0, 0, OnACDownloadFinished, false);
     return true;
 }
 /// <summary>
 /// One of the input subsystems has received a key or mouse press.
 /// </summary>
 /// <param name="qInstance"></param>
 /// <param name="parms"></param>
 /// <returns></returns>
 private bool ReceiveLater(DoLaterBase qInstance, Object parms) {
     Object[] loadParams = (Object[])parms;
     ReceiveUserIOInputEventTypeCode typ = (ReceiveUserIOInputEventTypeCode)loadParams[0];
     int param1 = (int)loadParams[1];
     float param2 = (float)loadParams[2];
     float param3 = (float)loadParams[3];
     
     switch (typ) {
         case ReceiveUserIOInputEventTypeCode.KeyPress:
             param1 = param1 & (int)Keys.KeyCode; // remove extra cruft
             this.UpdateModifier(param1, true);
             AddKeyToLastKeyCode(param1);
             m_log.Log(LogLevel.DVIEWDETAIL, "UserInterfaceCommon: ReceiveLater: KeyPress: {0}. LastKeyCode={1}", 
                             param1, this.LastKeyCode);
             this.m_repeatKey = param1;
             this.KeyPressed = true;
             this.m_repeatTimer.Change(this.KeyRepeatMs, this.KeyRepeatMs);
             if (this.OnUserInterfaceKeypress != null)
                 this.OnUserInterfaceKeypress(this.LastKeyCode, true);
             break;
         case ReceiveUserIOInputEventTypeCode.KeyRelease:
             param1 = param1 & (int)Keys.KeyCode; // remove extra cruft
             this.UpdateModifier(param1, false);
             AddKeyToLastKeyCode(param1);
             m_log.Log(LogLevel.DVIEWDETAIL, "UserInterfaceCommon: ReceiveLater: KeyRelease: {0}. LastKeyCode={1}", 
                             param1, this.LastKeyCode);
             this.KeyPressed = false;
             this.m_repeatTimer.Change(Timeout.Infinite, Timeout.Infinite);
             if (this.OnUserInterfaceKeypress != null)
                 this.OnUserInterfaceKeypress(this.LastKeyCode, false);
             break;
         case ReceiveUserIOInputEventTypeCode.MouseButtonDown:
             this.UpdateMouseModifier(param1, true);
             m_log.Log(LogLevel.DVIEWDETAIL, "UserInterfaceCommon: ReceiveLater: MouseBtnDown: {0}, {1}", 
                             param1, this.LastMouseButtons);
             if (this.OnUserInterfaceMouseButton != null) this.OnUserInterfaceMouseButton(
                             ThisMouseButtonCode(param1), true);
             break;
         case ReceiveUserIOInputEventTypeCode.MouseButtonUp:
             this.UpdateMouseModifier(param1, false);
             m_log.Log(LogLevel.DVIEWDETAIL, "UserInterfaceCommon: ReceiveLater: MouseBtnUp: {0}, {1}", 
                             param1, this.LastMouseButtons);
             if (this.OnUserInterfaceMouseButton != null) this.OnUserInterfaceMouseButton(
                             ThisMouseButtonCode(param1), false);
             break;
         case ReceiveUserIOInputEventTypeCode.MouseMove:
             // pass the routine tracking the raw position information
             // param1 is usually zero (actually mouse selector but we have only one at the moment)
             // param2 is the X movement
             // param3 is the Y movement
             if (this.OnUserInterfaceMouseMove != null) this.OnUserInterfaceMouseMove(param1, param2, param3);
             break;
     }
     // successful
     return true;
 }
 // Called on workqueue thread to create material in Ogre
 private bool RequestMaterialLater(DoLaterBase qInstance, Object parms)
 {
     Object[] loadParams = (Object[])parms;
     EntityNameOgre m_entName = (EntityNameOgre)loadParams[0];
     string m_matName = (string)loadParams[1];
     try {
     IEntity ent;
     if (World.World.Instance.TryGetEntity(m_entName, out ent)) {
         lock (ent) {
             LogManager.Log.Log(LogLevel.DRENDERDETAIL, "RequestMaterialLater.DoIt(): converting {0}", m_entName);
             IWorldRenderConv conver;
             if (!ent.TryGet<IWorldRenderConv>(out conver)) {
                 // the rendering context is not set up. Odd but not fatal
                 // try again later
                 return false;
             }
             else {
                 // Create the material resource and then make the rendering redisplay
                 conver.CreateMaterialResource(qInstance.priority, ent, m_matName);
                 Ogr.RefreshResourceBF(qInstance.priority, Ogr.ResourceTypeMaterial, m_matName);
             }
         }
     }
     else {
         // we couldn't find the entity for the material. not good
         m_log.Log(LogLevel.DBADERROR, "ProcessWaitingMaterials: could not find entity for material {0}", m_matName);
     }
     }
     catch (Exception e) {
     m_log.Log(LogLevel.DBADERROR, "ProcessWaitingMaterials: exception realizing material: {0}", e.ToString());
     }
     return true;
 }
 private bool DoUpdateLater(DoLaterBase qInstance, object parm)
 {
     object[] parms = (object[])parm;
     IEntity ent = (IEntity)parms[0];
     UpdateCodes detail = (UpdateCodes)parms[1];
     EntityUpdateCallback euc = OnEntityUpdate;
     if (euc != null) {
     euc(ent, detail);
     }
     return true;
 }
 private bool RequestTextureLater(DoLaterBase qInstance, Object parm)
 {
     EntityNameOgre m_entName = (EntityNameOgre)parm;
     // note the super kludge since we don't know the real asset context
     // This information is coded into the entity name
     // The callback can (and will) be called multiple times as the texture gets better resolution
     AssetContextBase.RequestTextureLoad(m_entName, AssetContextBase.AssetType.Texture, TextureLoadedCallback);
     return true;
 }
 // Doing the work didn't work the first time so we again add it to the queue
 public void DoLaterRequeue(DoLaterBase w)
 {
     AddWorkItemToQueue(w);
 }
 // Add a new work item to the work queue. If we don't have the maximum number
 // of worker threads already working on the queue, start a new thread from
 // the pool to empty the queue.
 private void AddWorkItemToQueue(DoLaterBase w)
 {
     if ((m_totalRequests++ % 100) == 0) {
     LogManager.Log.Log(LogLevel.DRENDERDETAIL, "{0}.DoLater: Queuing, c={1}, l={2}",
                     m_queueName, m_totalRequests, m_workItems.Count);
     }
     lock (m_workItems) {
     m_workItems.Enqueue(w);
     if (m_activeWorkProcessors < m_workProcessorsMax) {
         m_activeWorkProcessors++;
         ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoWork), null);
         // ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.DoWork), null);
     }
     }
 }
 // Doing the work didn't work the first time so we again add it to the queue
 public void DoLaterRequeue(DoLaterBase w)
 {
     AddWorkItemToQueue(w);
 }
 // IWorkQueue.DoLater()
 public void DoLater(DoLaterBase w)
 {
     w.containingClass = this;
     w.remainingWait = 0;    // the first time through, do it now
     w.timesRequeued = 0;
     AddWorkItemToQueue(w);
 }
 // requeuing the work item. Since requeuing, add the delay
 public void DoLaterRequeue(ref DoLaterBase w)
 {
     w.timesRequeued++;
     int nextTime = Math.Min(w.requeueWait * w.timesRequeued, 5000);
     w.remainingWait = System.Environment.TickCount + nextTime;
     AddToWorkQueue(w);
 }
 /// <summary>
 /// Add the work item to the queue in the order order
 /// </summary>
 /// <param name="w"></param>
 private void AddToWorkQueue(DoLaterBase w)
 {
     lock (m_workQueue) {
     /*
     // Experimental code trying to give some order to the requests
     LinkedListNode<DoLaterBase> foundItem = null;
     for (LinkedListNode<DoLaterBase> ii = m_workQueue.First; ii != null; ii = ii.Next) {
         if (w.order < ii.Value.order) {
             foundItem = ii;
             break;
         }
     }
     if (foundItem != null) {
         // we're pointing to an element to put our element before
         m_workQueue.AddBefore(foundItem, w);
     }
     else {
         // just put it on the end
         m_workQueue.AddLast(w);
     }
      */
     m_workQueue.AddLast(w);
     }
 }
        private bool DoRenderLater(DoLaterBase qInstance, Object parms)
        {
            Object[] loadParams = (Object[])parms;
            IEntity m_ent = (IEntity)loadParams[0];
            RenderableInfo m_ri = (RenderableInfo)loadParams[1];
            bool m_hasMesh = (bool)loadParams[2];
            // m_log.Log(LogLevel.DRENDERDETAIL, "DoRenderLater: ent={0}", m_ent.Name);

            IRenderEntity rEntity;
            if (m_ent.TryGet<IRenderEntity>(out rEntity)) {
            if (!rEntity.Create(ref m_ri, ref m_hasMesh, qInstance.priority, qInstance.timesRequeued)) {
                // Didn't want to create for some reason.
                // Remember progress flags and return 'false' so we get retried
                loadParams[1] = m_ri;
                loadParams[2] = m_hasMesh;
                return false;
            }
            }
            else {
            m_log.Log(LogLevel.DBADERROR, "DoRenderLater: entity had no renderEntity: {0}", m_ent.Name);
            }

            // System.GC.Collect(); // only for debugging
            return true;
        }
 public void DoLater(DoLaterBase w)
 {
     if (((m_totalRequests++) % 100) == 0) {
     LogManager.Log.Log(LogLevel.DRENDERDETAIL, "{0}.DoLater: Queuing. requests={1}, queueSize={2}",
         m_queueName, m_totalRequests, m_workQueue.Count);
     }
     w.containingClass = this;
     w.remainingWait = 0;    // the first time through, do it now
     w.timesRequeued = 0;
     AddToWorkQueue(w);
 }
        // Called on workQueue to call into gather parameters and create the mesh resource
        private bool RequestMeshLater(DoLaterBase qInstance, Object parm)
        {
            Object[] lparams = (Object[])parm;
            string m_meshName = (string)lparams[0];
            EntityName m_contextEntityName = (EntityName)lparams[1];
            try {
            // type information is at the end of the name. remove if there
            EntityName eName = EntityNameOgre.ConvertOgreResourceToEntityName(m_meshName);
            // the terrible kludge here is that the name of the mesh is the name of
            //   the entity. We reach back into the worlds and find the underlying
            //   entity then we can construct the mesh.
            IEntity ent;
            if (!World.World.Instance.TryGetEntity(eName, out ent)) {
                m_log.Log(LogLevel.DBADERROR, "RendererOgre.RequestMeshLater: could not find entity " + eName);
                lock (m_requestedMeshes) {
                    m_requestedMeshes.Remove(m_meshName);
                }
                return true;
            }
            // Create mesh resource. In this case its most likely a .mesh file in the cache
            // The actual mesh creation is queued and done later between frames
            lock (ent) {
                float priority = CalculateInterestOrder(ent);
                IWorldRenderConv conver;
                ent.TryGet<IWorldRenderConv>(out conver);
                if (!conver.CreateMeshResource(priority, ent, m_meshName, m_contextEntityName)) {
                    // we need to wait until some resource exists before we can complete this creation
                    return false;
                }

                // tell Ogre to refresh (reload) the resource
                m_log.Log(LogLevel.DRENDERDETAIL, "RendererOgre.RequestMeshLater: refresh for {0}. prio={1}",
                        m_meshName, priority);
                Ogr.RefreshResourceBF(priority, Ogr.ResourceTypeMesh, m_meshName);
            }
            // following code commented out to leave the mesh in the list and let it time out
            // lock (m_requestedMeshes) {
            //     m_requestedMeshes.Remove(m_meshName);
            // }
            }
            catch {
            // an oddity but not fatal
            }
            return true;
        }
 private bool DoEventLater(DoLaterBase qInstance, object parm)
 {
     EntityNewCallback enc = OnEntityNew;
     if (enc != null) {
     enc((IEntity)parm);
     }
     return true;
 }