public void AddItems () { var l = new UnorderedList<int>(); l.Add(0); l.Add(2); l.Add(3); l.Add(5); Assert.AreEqual( new int[] { 0, 2, 3, 5 }, l.ToArray() ); }
private HardwareBufferEntry PrepareToFillBuffer( HardwareBufferEntry currentBufferEntry, int vertexOffset, int indexOffset, int additionalVertexCount, int additionalIndexCount, bool forceExclusiveBuffer ) { var allocateNew = (currentBufferEntry == null) || forceExclusiveBuffer || CannotFitInBuffer(currentBufferEntry, additionalVertexCount, additionalIndexCount); if (allocateNew) { var newBuffer = AllocateSuitablySizedHardwareBuffer(additionalVertexCount, additionalIndexCount, forceExclusiveBuffer); HardwareBufferEntry entry; if (!_ReusableHardwareBufferEntries.TryPopFront(out entry)) { // FIXME: This frequently happens and it seems like the reason is that switching buffer generators to thread local // means that parallel prepare ends up creating dozens of generators that all fail to pool enough instances // Console.WriteLine("New entry"); entry = new HardwareBufferEntry(); } else { // Console.WriteLine("Reused entry"); } entry.Initialize( newBuffer, vertexOffset, indexOffset, additionalVertexCount, additionalIndexCount ); entry.AddedToList = true; _UsedHardwareBufferEntries.Add(entry); _FillingHardwareBufferEntry = entry; return(entry); } else { if (!currentBufferEntry.AddedToList) { currentBufferEntry.AddedToList = true; _UsedHardwareBufferEntries.Add(currentBufferEntry); } currentBufferEntry.SourceVertexCount += additionalVertexCount; currentBufferEntry.SourceIndexCount += additionalIndexCount; return(currentBufferEntry); } }
public void ReplicateFrom(Table source) { var sourceArray = source.ValuesById; var sourceCount = sourceArray.Count; var i = ValuesById.Count; while (i < sourceCount) { var item = sourceArray.DangerousGetItem(i); ValuesById.Add(item); IdsByValue[item] = i; i++; } }
public void MutableEnumeratorRemoveCurrentAndGetNext() { var l = new UnorderedList<int>(); l.Add(1); l.Add(2); int item; using (var e = l.GetEnumerator()) while (e.GetNext(out item)) e.RemoveCurrent(); Assert.AreEqual( new int[] { }, l.ToArray() ); }
/// <summary> /// Internal method to add an entity to the entity manager and register it with all /// associated systems. This executes the add immediately. /// </summary> /// <param name="toAdd">The entity to add.</param> private void InternalAddEntity(RuntimeEntity toAdd) { toAdd.GameEngine = this; // notify listeners that we both added and created the entity Log <GameEngine> .Info("Submitting internal EntityAddedEvent for " + toAdd); EventNotifier.Submit(EntityAddedEvent.Create(toAdd)); EventNotifier.Submit(ShowEntityEvent.Create(toAdd)); // add it to the entity index _entityIndex.AddEntity(toAdd); // register listeners toAdd.ModificationNotifier.Listener += OnEntityModified; toAdd.DataStateChangeNotifier.Listener += OnEntityDataStateChanged; // notify ourselves of data state changes so that it the entity is pushed to systems toAdd.DataStateChangeNotifier.Notify(); // ensure it contains metadata for our keys toAdd.Metadata.UnorderedListMetadata[_entityUnorderedListMetadataKey] = new UnorderedListMetadata(); // add it our list of entities _entities.Add(toAdd, GetEntitiesListFromMetadata(toAdd)); }
protected void MakeDrawArguments( PrimitiveType primitiveType, ref Internal.VertexBuffer <GeometryVertex> vb, ref Internal.IndexBuffer ib, ref int vertexOffset, ref int indexOffset, out int primCount, int vertexCount, int indexCount ) { if ((vertexCount == 0) || (indexCount == 0)) { primCount = 0; return; } primCount = primitiveType.ComputePrimitiveCount(indexCount); _DrawArguments.Add(new DrawArguments { PrimitiveType = primitiveType, VertexOffset = vertexOffset, VertexCount = vertexCount, IndexOffset = indexOffset, IndexCount = indexCount, PrimitiveCount = primCount }); vertexOffset += vertexCount; indexOffset += indexCount; }
private HardwareBufferEntry PrepareToFillBuffer( HardwareBufferEntry currentBufferEntry, int vertexOffset, int indexOffset, int additionalVertexCount, int additionalIndexCount, bool forceExclusiveBuffer ) { var allocateNew = (currentBufferEntry == null) || forceExclusiveBuffer || CannotFitInBuffer(currentBufferEntry, additionalVertexCount, additionalIndexCount); if (allocateNew) { var newBuffer = AllocateSuitablySizedHardwareBuffer(additionalVertexCount, additionalIndexCount); var entry = new HardwareBufferEntry(newBuffer, vertexOffset, indexOffset, additionalVertexCount, additionalIndexCount); _UsedHardwareBuffers.Add(entry); _FillingHardwareBufferEntry = entry; return(entry); } else { currentBufferEntry.SourceVertexCount += additionalVertexCount; currentBufferEntry.SourceIndexCount += additionalIndexCount; return(currentBufferEntry); } }
private T[] EnsureBufferCapacity <T> ( ref T[] array, ref int usedElementCount, int elementsToAdd, out int oldElementCount ) { oldElementCount = usedElementCount; int newElementCount = (usedElementCount += elementsToAdd); var oldArray = array; var oldArraySize = array.Length; if (oldArraySize >= newElementCount) { return(array); } var newSize = PickNewArraySize(oldArraySize, newElementCount); var newArray = new T[newSize]; _PendingCopies.Add(new PendingCopy { Source = oldArray, SourceIndex = 0, Destination = newArray, DestinationIndex = 0, Count = oldElementCount }); return(array = newArray); }
protected void BuildMaterialCache() { lock (Lock) { MaterialCacheScratchSet.Clear(); foreach (var field in AllMaterialFields) { var material = field(); if (material != null) { MaterialCacheScratchSet.Add(material); } } foreach (var coll in AllMaterialCollections) { coll()?.AddToSet(MaterialCacheScratchSet); } foreach (var m in ExtraMaterials) { MaterialCacheScratchSet.Add(m); } MaterialCache.Clear(); foreach (var m in MaterialCacheScratchSet) { MaterialCache.Add(m); } } }
public void Clear () { var l = new UnorderedList<int>(new int[] { 1, 2 }); l.Clear(); Assert.AreEqual( new int[0], l.ToArray() ); l.Add(1); l.Add(2); Assert.AreEqual( new int[] { 1, 2 }, l.ToArray() ); }
public void Release(ref UnorderedList <T> _list) { var list = _list; _list = null; if (list == null) { return; } if (list.Capacity > MaxItemCapacity) { lock (_LargePool) { if (_LargePool.Count >= LargePoolCapacity) { return; } _LargePool.Add(list); } return; } lock (_Pool) { if (_Pool.Count >= PoolCapacity) { return; } _Pool.Add(list); } }
protected override void Initialize() { base.Initialize(); var rng = new Random(); float now = (float)Time.Seconds; for (int i = 0; i < NumberOfOrbs; i++) { Orbs.Add(new Orb(rng, now - (float)rng.NextDouble())); } RNGSeed = rng.Next(); RNGs.Add(rng); }
public void Write(Vector2 a, Vector2 b) { if (CropBounds.HasValue && Geometry.DoesLineIntersectRectangle(a, b, CropBounds.Value)) { return; } Lines.Add(new DeltaLine(a, b)); }
private void RunPendingDraws() { lock (PendingDrawQueue) if (PendingDrawQueue.Count == 0) { return; } int i = 0; BatchGroup bg = null; while (true) { PendingDraw pd; lock (PendingDrawQueue) { if (PendingDrawQueue.Count == 0) { return; } pd = PendingDrawQueue.DangerousGetItem(0); PendingDrawQueue.DangerousRemoveAt(0); } ExceptionDispatchInfo excInfo = null; try { if (!AutoRenderTarget.IsRenderTargetValid(pd.RenderTarget)) { throw new Exception("Render target for pending draw was disposed between queue and prepare"); } if (!DoSynchronousDrawToRenderTarget(pd.RenderTarget, pd.Materials, pd.Handler, pd.UserData, ref pd.ViewTransform, "Pending Draw")) { throw new Exception("Unknown failure performing pending draw"); } } catch (Exception exc) { excInfo = ExceptionDispatchInfo.Capture(exc); } // throw new Exception("Unexpected error performing pending draw"); if (pd.OnComplete != null) { lock (CompletedPendingDrawQueue) CompletedPendingDrawQueue.Add(new CompletedPendingDraw { UserData = pd.UserData, Exception = excInfo, OnComplete = pd.OnComplete, RenderTarget = pd.RenderTarget }); } else if (excInfo != null) { } else { } } }
public virtual void Release(T obj) { lock (_Pool) { if (_Pool.Count > PoolCapacity) { return; } _Pool.Add(obj); } }
private void OnClearListDrained(int listsCleared, bool moreRemain) { var cl = ClearedLists.Value; if (cl == null) // FIXME: This shouldn't be possible { return; } bool isSmallLocked = false, isLargeLocked = false; try { foreach (var item in cl) { var isLarge = (item.Capacity > SmallPoolMaxItemSize); if (isLarge) { if (!isLargeLocked) { isLargeLocked = true; Monitor.Enter(_LargePool); } if (_LargePool.Count >= LargePoolCapacity) { continue; } _LargePool.Add(item); } else { if (!isSmallLocked) { isSmallLocked = true; Monitor.Enter(_Pool); } if (_Pool.Count >= SmallPoolCapacity) { continue; } _Pool.Add(item); } } } finally { if (isSmallLocked) { Monitor.Exit(_Pool); } if (isLargeLocked) { Monitor.Exit(_LargePool); } cl.Clear(); } }
public void Release(ref UnorderedList <T> _list) { var list = _list; _list = null; if (list == null) { return; } if (list.Capacity > SmallPoolMaxItemSize) { if (list.Capacity < LargePoolMaxItemSize) { lock (_LargePool) { if (_LargePool.Count >= LargePoolCapacity) { return; } } list.Clear(); lock (_LargePool) { if (_LargePool.Count >= LargePoolCapacity) { return; } _LargePool.Add(list); } } return; } lock (_Pool) { if (_Pool.Count >= SmallPoolCapacity) { return; } } list.Clear(); lock (_Pool) { if (_Pool.Count >= SmallPoolCapacity) { return; } _Pool.Add(list); } }
void IBufferGenerator.Reset() { lock (_StateLock) { _FillingHardwareBufferEntry = null; _FlushedToBuffers = 0; _VertexCount = _IndexCount = 0; // Any buffers that remain unused (either from being too small, or being unnecessary now) // should be disposed. THardwareBuffer hb; using (var e = _UnusedHardwareBuffers.GetEnumerator()) while (e.GetNext(out hb)) { hb.Age += 1; bool shouldKill = (hb.Age >= MaxBufferAge) || ((_UnusedHardwareBuffers.Count > MaxUnusedBuffers) && (hb.Age > 1)); if (shouldKill) { e.RemoveCurrent(); hb.Invalidate(); DisposeResource(hb); } } // Return any buffers that were used this frame to the unused state. foreach (var _hb in _UsedHardwareBuffers) { // HACK var hwb = _hb.Buffer; hwb.Invalidate(); _UnusedHardwareBuffers.Add(hwb); } _UsedHardwareBuffers.Clear(); foreach (var swb in _SoftwareBuffers) { swb.Uninitialize(); _SoftwareBufferPool.Release(swb); } _SoftwareBuffers.Clear(); /* * Array.Clear(_VertexArray, 0, _VertexArray.Length); * Array.Clear(_IndexArray, 0, _IndexArray.Length); */ } }
public void UpdateItems() { var items = (from kvp in ListsByPriority orderby kvp.Key descending select kvp.Value); Items.Clear(); foreach (var l in items) { var buf = new IWorkQueue[l.Count]; l.CopyTo(buf, 0, l.Count); Items.Add(buf); } }
private void CreateList(int?capacity = null) { if (!capacity.HasValue) { capacity = ListCapacity; } _HasList = true; if (ListPool != null) { Items = ListPool.Allocate(capacity); } else if (capacity.HasValue) { Items = new UnorderedList <T>(capacity.Value); } else { Items = new UnorderedList <T>(); } if (_Count > 0) { Items.Add(ref Item1); } if (_Count > 1) { Items.Add(ref Item2); } if (_Count > 2) { Items.Add(ref Item3); } if (_Count > 3) { Items.Add(ref Item4); } Item1 = Item2 = Item3 = Item4 = default(T); _Count = 0; }
protected void UpdateSector(ParticleCollection sector) { if (sector.Count == 0) { return; } _SectorsFromLastUpdate.Add(sector); UpdateArgs.SetSector(sector); Updater(UpdateArgs); UpdateArgs.Enumerator.Dispose(); }
private void AddChild(CancellationScope child) { if (this == Null) { return; } if (Children == null) { Children = new UnorderedList <CancellationScope>(); } Children.Add(child); }
/// <summary> /// You can use this to request a work queue for a given type of work item, then queue /// multiple items cheaply. If you queue items directly, it's your responsibility to call /// ThreadGroup.NotifyQueuesChanged to ensure that a sufficient number of threads are ready /// to perform work. /// </summary> /// <param name="forMainThread">Pass true if you wish to queue a work item to run on the main thread. Will be set automatically for main-thread-only work items.</param> public WorkQueue <T> GetQueueForType <T> (bool forMainThread = false) where T : IWorkItem { var type = typeof(T); bool resultIsNew; IWorkQueue existing; WorkQueue <T> result; var queues = Queues; var isMainThreadOnly = typeof(IMainThreadWorkItem).IsAssignableFrom(type) || forMainThread; // If the job must be run on the main thread, add to the main thread queue // Note that you must manually pump this queue yourself. if (isMainThreadOnly) { queues = MainThreadQueues; } lock (queues) { if (!queues.TryGetValue(type, out existing)) { result = CreateQueueForType <T>(isMainThreadOnly); queues.Add(type, result); resultIsNew = true; } else { result = (WorkQueue <T>)existing; resultIsNew = false; } } if (isMainThreadOnly && resultIsNew) { lock (MainThreadQueueList) MainThreadQueueList.Add(result); } if (resultIsNew) { NewQueueCreated(); NotifyQueuesChanged(); } return(result); }
public void Write(Vector2 a, Vector2 b) { if (CropBounds.HasValue) { // constructor doesn't get inlined here :( Bounds lineBounds; lineBounds.TopLeft.X = Math.Min(a.X, b.X); lineBounds.TopLeft.Y = Math.Min(a.Y, b.Y); lineBounds.BottomRight.X = Math.Max(a.X, b.X); lineBounds.BottomRight.Y = Math.Max(a.Y, b.Y); if (!CropBounds.Value.Intersects(lineBounds)) { return; } } Lines.Add(new Line(a, b)); }
public void Add(Batch batch) { if (State != State_Initialized) { throw new InvalidOperationException(); } lock (Batches) { #if DEBUG && PARANOID if (Batches.Contains(batch)) { throw new InvalidOperationException("Batch already added to this frame"); } #endif Batches.Add(batch); batch.Container = this; } }
public void Reset(int frameIndex) { lock (_StateLock) { _FillingHardwareBufferEntry = null; _FlushedToBuffers = 0; _VertexCount = _IndexCount = 0; lock (_StaticStateLock) StaticReset(frameIndex, RenderManager); // Return any buffers that were used this frame to the unused state. foreach (var _hb in _UsedHardwareBuffers) { // HACK var hwb = _hb.Buffer; hwb.Invalidate(frameIndex); lock (_StaticStateLock) _UnusedHardwareBuffers.Add(hwb); } _UsedHardwareBuffers.Clear(); _BufferCache.Clear(); foreach (var swb in _SoftwareBuffers) { swb.Uninitialize(); _SoftwareBufferPool.Release(swb); } _SoftwareBuffers.Clear(); _LastFrameReset = frameIndex; /* * Array.Clear(_VertexArray, 0, _VertexArray.Length); * Array.Clear(_IndexArray, 0, _IndexArray.Length); */ } }
private void SpawnThread(bool force) { // Just in case thread state gets out of sync... if ((Threads.Count >= MaximumThreadCount) && !force) { return; } Interlocked.Exchange(ref LastTimeThreadWasIdle, TimeProvider.Ticks); var thread = new GroupThread(this); Threads.Add(thread); Thread.MemoryBarrier(); HasNoThreads = false; CanMakeNewThreads = Threads.Count < MaximumThreadCount; CurrentThreadCount = Threads.Count; Thread.MemoryBarrier(); }
private void ClearAndReturn(UnorderedList <T> list, UnorderedList <UnorderedList <T> > pool, int limit, WorkQueueNotifyMode notifyMode = WorkQueueNotifyMode.Stochastically) { if ( !FastClearEnabled && (list.Count > DeferredClearSizeThreshold) && (_ClearQueue != null) ) { _ClearQueue.Enqueue(new ListClearWorkItem { List = list, }, notifyChanged: notifyMode); return; } // HACK: Acquiring the lock here would be technically correct but // unnecessarily slow, since we're just trying to avoid doing a clear // in cases where the list will be GCd anyway if (pool.Count >= limit) { return; } if (FastClearEnabled) { list.UnsafeFastClear(); } else { list.Clear(); } lock (pool) { if (pool.Count >= limit) { return; } pool.Add(list); } }
/// <summary> /// Queues a draw operation to the specified render target. /// The draw operation will occur at the start of the next frame before the frame itself has been rendered. /// </summary> /// <param name="onComplete">A Future instance that will have userData stored into it when rendering is complete.</param> public bool QueueDrawToRenderTarget( RenderTarget2D renderTarget, DefaultMaterialSet materials, PendingDrawHandler handler, object userData = null, IFuture onComplete = null, ViewTransform?viewTransform = null ) { if (!ValidateDrawToRenderTarget(renderTarget, materials)) { return(false); } lock (PendingDrawQueue) PendingDrawQueue.Add(new PendingDraw { RenderTarget = renderTarget, Handler = handler, Materials = materials, UserData = userData, OnComplete = onComplete, ViewTransform = viewTransform }); return(true); }
/// <summary> /// Updates the status of the entity inside of the cache; ie, if the entity is now /// passing the filter but was not before, then it will be added to the cache. /// </summary> /// <returns>The change in cache status for the entity</returns> public CacheChangeResult UpdateCache(RuntimeEntity entity) { UnorderedListMetadata metadata = GetMetadata(entity); bool passed = _filter.Check(entity); bool contains = CachedEntities.Contains(entity, metadata); // The entity is not in the cache it now passes the filter, so add it to the cache if (contains == false && passed) { CachedEntities.Add(entity, metadata); return(CacheChangeResult.Added); } // The entity is in the cache but it no longer passes the filter, so remove it if (contains && passed == false) { CachedEntities.Remove(entity, metadata); return(CacheChangeResult.Removed); } // no change to the cache return(CacheChangeResult.NoChange); }
/// <summary> /// Allocates a software vertex/index buffer pair that you can write vertices and indices into. /// Once this generator is flushed, it will have an associated hardware buffer containing your vertex/index data. /// </summary> /// <param name="vertexCount">The number of vertices.</param> /// <param name="indexCount">The number of indices.</param> /// <param name="forceExclusiveBuffer">Forces a unique hardware vertex/index buffer pair to be created for this allocation. This allows you to ignore the hardware vertex/index offsets.</param> /// <returns>A software buffer.</returns> public SoftwareBuffer Allocate(int vertexCount, int indexCount, bool forceExclusiveBuffer = false) { var requestedVertexCount = vertexCount; var requestedIndexCount = indexCount; if (vertexCount > MaxVerticesPerHardwareBuffer) { throw new ArgumentOutOfRangeException("vertexCount", vertexCount, "Maximum vertex count on this platform is " + MaxVerticesPerHardwareBuffer); } lock (_StateLock) { if (_FlushedToBuffers != 0) { throw new InvalidOperationException("Already flushed"); } // When we resize our internal array, we have to queue up copies from the old small array to the new large array. // This is because while Allocate is thread-safe, consumers are allowed to write to their allocations without synchronization, // so we can't be sure that the old array has been filled yet. // Flush() is responsible for doing all these copies to ensure that all vertex data eventually makes it into the large array // from which actual hardware buffer initialization occurs. SoftwareBuffer swb; int oldVertexCount, oldIndexCount; var didArraysChange = EnsureBufferCapacity( ref _VertexArray, ref _VertexCount, vertexCount, out oldVertexCount ); if (EnsureBufferCapacity( ref _IndexArray, ref _IndexCount, indexCount, out oldIndexCount )) { didArraysChange = true; } if (didArraysChange) { foreach (var _swb in _SoftwareBuffers) { _swb.ArraysChanged(_VertexArray, _IndexArray); } } HardwareBufferEntry hardwareBufferEntry; hardwareBufferEntry = _FillingHardwareBufferEntry; hardwareBufferEntry = PrepareToFillBuffer( hardwareBufferEntry, oldVertexCount, oldIndexCount, vertexCount, indexCount, forceExclusiveBuffer ); int oldHwbVerticesUsed, oldHwbIndicesUsed; oldHwbVerticesUsed = hardwareBufferEntry.VerticesUsed; oldHwbIndicesUsed = hardwareBufferEntry.IndicesUsed; hardwareBufferEntry.VerticesUsed += vertexCount; hardwareBufferEntry.IndicesUsed += indexCount; hardwareBufferEntry.SoftwareBufferCount += 1; swb = _SoftwareBufferPool.Allocate(); if (swb.IsInitialized) { throw new ThreadStateException(); } swb.Initialize( new ArraySegment <TVertex>(_VertexArray, hardwareBufferEntry.VertexOffset + oldHwbVerticesUsed, requestedVertexCount), new ArraySegment <TIndex>(_IndexArray, hardwareBufferEntry.IndexOffset + oldHwbIndicesUsed, requestedIndexCount), hardwareBufferEntry.Buffer, oldHwbVerticesUsed, oldHwbIndicesUsed ); _SoftwareBuffers.Add(swb); return(swb); } }
public void Reset(int frameIndex) { var id = RenderManager.DeviceManager.DeviceId; lock (_StateLock) { _FillingHardwareBufferEntry = null; _FlushedToBuffers = 0; _VertexCount = _IndexCount = 0; lock (_StaticStateLock) StaticReset(frameIndex, RenderManager); foreach (var _hb in _PreviouslyUsedHardwareBufferEntries) { _ReusableHardwareBufferEntries.Add(_hb); } _PreviouslyUsedHardwareBufferEntries.Clear(); // Return any buffers that were used this frame to the unused state. foreach (var _hb in _UsedHardwareBufferEntries) { _hb.AddedToList = false; _PreviouslyUsedHardwareBufferEntries.Add(_hb); // HACK var hwb = _hb.Buffer; if (hwb.DeviceId < id) { continue; } hwb.Invalidate(frameIndex); lock (_StaticStateLock) { if (hwb.IsLarge) { _LargeUnusedBufferCount++; } else { _SmallUnusedBufferCount++; } _UnusedHardwareBuffers.Add(hwb); } } _UsedHardwareBufferEntries.Clear(); foreach (var kvp in _BufferCache) { var swb = kvp.Value; if (!swb.IsInitialized) { continue; } // Console.WriteLine($"Uninit corner buffer {swb}"); swb.Uninitialize(); _SoftwareBufferPool.Release(swb); } _BufferCache.Clear(); foreach (var swb in _SoftwareBuffers) { if (!swb.IsInitialized) { continue; } swb.Uninitialize(); _SoftwareBufferPool.Release(swb); } _SoftwareBuffers.Clear(); _LastFrameReset = frameIndex; /* * Array.Clear(_VertexArray, 0, _VertexArray.Length); * Array.Clear(_IndexArray, 0, _IndexArray.Length); */ } }
/// <param name="onException">To avoid generating garbage use a static method or a cached delegate</param> public void Execute() { defaultTimer.UpdateCurrentTime(); timesUpdated++; for (int i = 0; i < recurrentCallbacks.Length; i++) { var queue = recurrentCallbacks[i]; // Execute Delayed Deletes var delayedQueue = delayedRemoves[i]; if (delayedQueue.Count > 0) { lock (delayedQueue) { while (delayedQueue.Count > 0) { var reference = delayedQueue.ExtractLast(); for (int j = 0; j < queue.Count; j++) { if (queue.elements[j].id == reference.id) { queue.RemoveAt(j); break; } } } } } for (int j = 0; j < queue.Count; j++) { try { queue.elements[j].action(); } catch (Exception e) { Console.WriteLine(e); if (null != onException) { onException.Invoke(e); } } } } for (int i = 0; i < actionsStillWaiting.Count;) { if (actionsStillWaiting[i].ItsTime()) { try { actionsStillWaiting[i].action.Invoke(); } catch (Exception e) { Console.WriteLine(e); if (null != onException) { onException.Invoke(e); } } finally { actionsStillWaiting.RemoveAt(i); } } else { i++; } } TimedAction timedAction; while (queuedUpdateCallbacks.Dequeue(out timedAction)) { if (timedAction.ItsTime()) { try { timedAction.action(); } catch (Exception e) { Console.WriteLine(e); if (null != onException) { onException.Invoke(e); } } } else { actionsStillWaiting.Add(timedAction); } } }