implement Filter
parent
272fd6b492
commit
b3ff7e3f1c
|
@ -13,13 +13,12 @@ namespace MoonTools.ECS.Rev2
|
||||||
new Dictionary<ComponentId, int>();
|
new Dictionary<ComponentId, int>();
|
||||||
public SortedDictionary<ComponentId, ArchetypeEdge> Edges = new SortedDictionary<ComponentId, ArchetypeEdge>();
|
public SortedDictionary<ComponentId, ArchetypeEdge> Edges = new SortedDictionary<ComponentId, ArchetypeEdge>();
|
||||||
|
|
||||||
public int Count;
|
public int Count => RowToEntity.Count;
|
||||||
|
|
||||||
public Archetype(ArchetypeId id, ArchetypeSignature signature)
|
public Archetype(ArchetypeId id, ArchetypeSignature signature)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Signature = signature;
|
Signature = signature;
|
||||||
Count = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,161 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MoonTools.ECS.Rev2
|
namespace MoonTools.ECS.Rev2
|
||||||
{
|
{
|
||||||
// TODO: do we want to get fancy with queries beyond Include and Exclude?
|
// TODO: do we want to get fancy with queries beyond Include and Exclude?
|
||||||
// TODO: need an edge iterator as part of this nested horseshit
|
|
||||||
public class Filter
|
public class Filter
|
||||||
{
|
{
|
||||||
private Archetype Start;
|
private Archetype EmptyArchetype;
|
||||||
|
private HashSet<ComponentId> Included;
|
||||||
|
private HashSet<ComponentId> Excluded;
|
||||||
|
|
||||||
public EntityEnumerator Entities => new EntityEnumerator(Start);
|
public EntityEnumerator Entities => new EntityEnumerator(this);
|
||||||
|
internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
|
||||||
|
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
|
||||||
|
|
||||||
private ref struct FilterEnumerator
|
public bool Empty
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var empty = true;
|
||||||
|
|
||||||
|
foreach (var archetype in Archetypes)
|
||||||
|
{
|
||||||
|
if (archetype.Count > 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
foreach (var archetype in Archetypes)
|
||||||
|
{
|
||||||
|
count += archetype.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: this WILL crash if the index is out of range!
|
||||||
|
public EntityId NthEntity(int index)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
foreach (var archetype in Archetypes)
|
||||||
|
{
|
||||||
|
count += archetype.Count;
|
||||||
|
if (index < count)
|
||||||
|
{
|
||||||
|
return archetype.RowToEntity[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
index -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Filter index out of range!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityId RandomEntity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var randomIndex = RandomManager.Next(Count);
|
||||||
|
return NthEntity(randomIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Filter(Archetype emptyArchetype, HashSet<ComponentId> included, HashSet<ComponentId> excluded)
|
||||||
|
{
|
||||||
|
EmptyArchetype = emptyArchetype;
|
||||||
|
Included = included;
|
||||||
|
Excluded = excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ref struct ArchetypeEnumerator
|
||||||
{
|
{
|
||||||
private Archetype CurrentArchetype;
|
private Archetype CurrentArchetype;
|
||||||
private bool Active;
|
private bool Active;
|
||||||
|
|
||||||
public FilterEnumerator(Archetype start)
|
// TODO: pool these
|
||||||
|
private Queue<Archetype> ArchetypeQueue = new Queue<Archetype>();
|
||||||
|
private Queue<Archetype> ArchetypeSearchQueue = new Queue<Archetype>();
|
||||||
|
private HashSet<Archetype> Explored = new HashSet<Archetype>();
|
||||||
|
|
||||||
|
public ArchetypeEnumerator GetEnumerator() => this;
|
||||||
|
|
||||||
|
public ArchetypeEnumerator(Filter filter)
|
||||||
{
|
{
|
||||||
CurrentArchetype = start;
|
|
||||||
Active = false;
|
Active = false;
|
||||||
|
|
||||||
|
var empty = filter.EmptyArchetype;
|
||||||
|
ArchetypeSearchQueue.Enqueue(empty);
|
||||||
|
|
||||||
|
while (ArchetypeSearchQueue.TryDequeue(out var current))
|
||||||
|
{
|
||||||
|
// exclude the empty archetype
|
||||||
|
var satisfiesFilter = filter.Included.Count != 0;
|
||||||
|
|
||||||
|
foreach (var componentId in filter.Included)
|
||||||
|
{
|
||||||
|
if (!current.ComponentToColumnIndex.ContainsKey(componentId))
|
||||||
|
{
|
||||||
|
satisfiesFilter = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var componentId in filter.Excluded)
|
||||||
|
{
|
||||||
|
if (current.ComponentToColumnIndex.ContainsKey(componentId))
|
||||||
|
{
|
||||||
|
satisfiesFilter = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (satisfiesFilter)
|
||||||
|
{
|
||||||
|
ArchetypeQueue.Enqueue(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the current archetype satisfies the filter, we need to add all edges that
|
||||||
|
// do not have an excluded component
|
||||||
|
// if the current archetype does not satisfy the filter, we need to add all edges that
|
||||||
|
// include an included component
|
||||||
|
foreach (var (componentId, edge) in current.Edges)
|
||||||
|
{
|
||||||
|
if (satisfiesFilter)
|
||||||
|
{
|
||||||
|
if (!filter.Excluded.Contains(componentId) && !Explored.Contains(edge.Add))
|
||||||
|
{
|
||||||
|
Explored.Add(edge.Add);
|
||||||
|
ArchetypeSearchQueue.Enqueue(edge.Add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (filter.Included.Contains(componentId) && !Explored.Contains(edge.Add))
|
||||||
|
{
|
||||||
|
Explored.Add(edge.Add);
|
||||||
|
ArchetypeSearchQueue.Enqueue(edge.Add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (!Active)
|
return ArchetypeQueue.TryDequeue(out CurrentArchetype!);
|
||||||
{
|
|
||||||
Active = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: go to next available edge
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Archetype Current => CurrentArchetype;
|
public Archetype Current => CurrentArchetype;
|
||||||
|
@ -38,39 +163,50 @@ namespace MoonTools.ECS.Rev2
|
||||||
|
|
||||||
public ref struct EntityEnumerator
|
public ref struct EntityEnumerator
|
||||||
{
|
{
|
||||||
private FilterEnumerator FilterEnumerator;
|
private EntityId CurrentEntity;
|
||||||
private ReverseSpanEnumerator<EntityId> EntityListEnumerator;
|
|
||||||
private bool EntityListEnumeratorActive;
|
|
||||||
|
|
||||||
public EntityEnumerator GetEnumerator() => this;
|
public EntityEnumerator GetEnumerator() => this;
|
||||||
|
|
||||||
public EntityEnumerator(Archetype start)
|
// TODO: pool this
|
||||||
|
Queue<EntityId> EntityQueue = new Queue<EntityId>();
|
||||||
|
|
||||||
|
internal EntityEnumerator(Filter filter)
|
||||||
{
|
{
|
||||||
FilterEnumerator = new FilterEnumerator(start);
|
var archetypeEnumerator = new ArchetypeEnumerator(filter);
|
||||||
|
|
||||||
|
foreach (var archetype in archetypeEnumerator)
|
||||||
|
{
|
||||||
|
foreach (var entity in archetype.RowToEntity)
|
||||||
|
{
|
||||||
|
EntityQueue.Enqueue(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (!EntityListEnumeratorActive || !EntityListEnumerator.MoveNext())
|
return EntityQueue.TryDequeue(out CurrentEntity);
|
||||||
{
|
|
||||||
if (!FilterEnumerator.MoveNext())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FilterEnumerator.Current.RowToEntity.Count != 0)
|
|
||||||
{
|
|
||||||
EntityListEnumerator = new ReverseSpanEnumerator<EntityId>(CollectionsMarshal.AsSpan(FilterEnumerator.Current.RowToEntity));
|
|
||||||
EntityListEnumeratorActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityId Current => EntityListEnumerator.Current;
|
public EntityId Current => CurrentEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref struct RandomEntityEnumerator
|
||||||
|
{
|
||||||
|
private Filter Filter;
|
||||||
|
private LinearCongruentialEnumerator LinearCongruentialEnumerator;
|
||||||
|
|
||||||
|
public RandomEntityEnumerator GetEnumerator() => this;
|
||||||
|
|
||||||
|
internal RandomEntityEnumerator(Filter filter)
|
||||||
|
{
|
||||||
|
Filter = filter;
|
||||||
|
LinearCongruentialEnumerator =
|
||||||
|
RandomManager.LinearCongruentialSequence(filter.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext() => LinearCongruentialEnumerator.MoveNext();
|
||||||
|
public EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace MoonTools.ECS.Rev2
|
||||||
IdAssigner<EntityId> EntityIdAssigner = new IdAssigner<EntityId>();
|
IdAssigner<EntityId> EntityIdAssigner = new IdAssigner<EntityId>();
|
||||||
IdAssigner<ComponentId> ComponentIdAssigner = new IdAssigner<ComponentId>();
|
IdAssigner<ComponentId> ComponentIdAssigner = new IdAssigner<ComponentId>();
|
||||||
|
|
||||||
|
public readonly Archetype EmptyArchetype;
|
||||||
|
|
||||||
private bool IsDisposed;
|
private bool IsDisposed;
|
||||||
|
|
||||||
public delegate void RefAction<T1, T2>(ref T1 arg1, ref T2 arg2);
|
public delegate void RefAction<T1, T2>(ref T1 arg1, ref T2 arg2);
|
||||||
|
@ -34,7 +36,7 @@ namespace MoonTools.ECS.Rev2
|
||||||
public World()
|
public World()
|
||||||
{
|
{
|
||||||
// Create the Empty Archetype
|
// Create the Empty Archetype
|
||||||
CreateArchetype(ArchetypeSignature.Empty);
|
EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Archetype CreateArchetype(ArchetypeSignature signature)
|
private Archetype CreateArchetype(ArchetypeSignature signature)
|
||||||
|
@ -62,7 +64,7 @@ namespace MoonTools.ECS.Rev2
|
||||||
{
|
{
|
||||||
var entityId = EntityIdAssigner.Assign();
|
var entityId = EntityIdAssigner.Assign();
|
||||||
var emptyArchetype = ArchetypeIndex[ArchetypeSignature.Empty];
|
var emptyArchetype = ArchetypeIndex[ArchetypeSignature.Empty];
|
||||||
EntityIndex.Add(entityId, new Record(emptyArchetype, 0));
|
EntityIndex.Add(entityId, new Record(emptyArchetype, emptyArchetype.Count));
|
||||||
emptyArchetype.RowToEntity.Add(entityId);
|
emptyArchetype.RowToEntity.Add(entityId);
|
||||||
return entityId;
|
return entityId;
|
||||||
}
|
}
|
||||||
|
@ -210,14 +212,15 @@ namespace MoonTools.ECS.Rev2
|
||||||
archetype.Components[i].Delete(row);
|
archetype.Components[i].Delete(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (archetype.Count > 1)
|
if (row != archetype.Count - 1)
|
||||||
{
|
{
|
||||||
// update row to entity lookup on archetype
|
// move last row entity to open spot
|
||||||
archetype.RowToEntity[row] = archetype.RowToEntity[archetype.Count - 1];
|
var lastRowEntity = archetype.RowToEntity[archetype.Count - 1];
|
||||||
archetype.RowToEntity.RemoveAt(archetype.Count - 1);
|
archetype.RowToEntity[row] = lastRowEntity;
|
||||||
|
EntityIndex[lastRowEntity] = new Record(archetype, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
archetype.Count -= 1;
|
archetype.RowToEntity.RemoveAt(archetype.Count - 1);
|
||||||
EntityIndex.Remove(entityId);
|
EntityIndex.Remove(entityId);
|
||||||
EntityIdAssigner.Unassign(entityId);
|
EntityIdAssigner.Unassign(entityId);
|
||||||
}
|
}
|
||||||
|
@ -236,20 +239,19 @@ namespace MoonTools.ECS.Rev2
|
||||||
from.Components[i].Delete(row);
|
from.Components[i].Delete(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from.Count > 1)
|
if (row != from.Count - 1)
|
||||||
{
|
{
|
||||||
// update row to entity lookup on from archetype
|
// move last row entity to open spot
|
||||||
from.RowToEntity[row] = from.RowToEntity[from.Count - 1];
|
var lastRowEntity = from.RowToEntity[from.Count - 1];
|
||||||
from.RowToEntity.RemoveAt(from.Count - 1);
|
from.RowToEntity[row] = lastRowEntity;
|
||||||
EntityIndex[from.RowToEntity[row]] = new Record(from, row);
|
EntityIndex[lastRowEntity] = new Record(from, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
from.RowToEntity.RemoveAt(from.Count - 1);
|
||||||
|
|
||||||
// update row to entity lookup on to archetype
|
// update row to entity lookup on to archetype
|
||||||
EntityIndex[entityId] = new Record(to, to.Count);
|
EntityIndex[entityId] = new Record(to, to.Count);
|
||||||
to.RowToEntity.Add(entityId);
|
to.RowToEntity.Add(entityId);
|
||||||
|
|
||||||
to.Count += 1;
|
|
||||||
from.Count -= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed)
|
private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed)
|
||||||
|
@ -269,101 +271,60 @@ namespace MoonTools.ECS.Rev2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from.Count > 1)
|
if (row != from.Count - 1)
|
||||||
{
|
{
|
||||||
// update row to entity lookup on from archetype
|
// update row to entity lookup on from archetype
|
||||||
from.RowToEntity[row] = from.RowToEntity[from.Count - 1];
|
var lastRowEntity = from.RowToEntity[from.Count - 1];
|
||||||
from.RowToEntity.RemoveAt(from.Count - 1);
|
from.RowToEntity[row] = lastRowEntity;
|
||||||
EntityIndex[from.RowToEntity[row]] = new Record(from, row);
|
EntityIndex[lastRowEntity] = new Record(from, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
from.RowToEntity.RemoveAt(from.Count - 1);
|
||||||
|
|
||||||
// update row to entity lookup on to archetype
|
// update row to entity lookup on to archetype
|
||||||
EntityIndex[entityId] = new Record(to, to.Count);
|
EntityIndex[entityId] = new Record(to, to.Count);
|
||||||
to.RowToEntity.Add(entityId);
|
to.RowToEntity.Add(entityId);
|
||||||
|
|
||||||
to.Count += 1;
|
|
||||||
from.Count -= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void ForEachEntity<T, T1, T2>(ArchetypeSignature signature,
|
public unsafe void ForEachEntity<T, T1, T2>(Filter filter,
|
||||||
T rowForEachContainer) where T : IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged
|
T rowForEachContainer) where T : IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged
|
||||||
{
|
{
|
||||||
var archetype = ArchetypeIndex[signature];
|
foreach (var archetype in filter.Archetypes)
|
||||||
|
|
||||||
var componentIdOne = signature[0];
|
|
||||||
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
|
|
||||||
var columnOneElements = archetype.Components[columnIndexOne].Elements;
|
|
||||||
|
|
||||||
var componentIdTwo = signature[1];
|
|
||||||
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
|
|
||||||
var columnTwoElements = archetype.Components[columnIndexTwo].Elements;
|
|
||||||
|
|
||||||
for (int i = archetype.Count - 1; i >= 0; i -= 1)
|
|
||||||
{
|
{
|
||||||
rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
|
var componentIdOne = archetype.Signature[0];
|
||||||
}
|
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
|
||||||
|
var columnOneElements = archetype.Components[columnIndexOne].Elements;
|
||||||
|
|
||||||
foreach (var edge in archetype.Edges.Values)
|
var componentIdTwo = archetype.Signature[1];
|
||||||
{
|
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
|
||||||
if (edge.Add != archetype)
|
var columnTwoElements = archetype.Components[columnIndexTwo].Elements;
|
||||||
|
|
||||||
|
for (int i = archetype.Count - 1; i >= 0; i -= 1)
|
||||||
{
|
{
|
||||||
ForEachEntity<T, T1, T2>(edge.Add.Signature, rowForEachContainer);
|
rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void ForEachEntity<T1, T2>(ArchetypeSignature signature, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged
|
public unsafe void ForEachEntity<T1, T2>(Filter filter, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged
|
||||||
{
|
{
|
||||||
var archetype = ArchetypeIndex[signature];
|
foreach (var archetype in filter.Archetypes)
|
||||||
|
|
||||||
var componentIdOne = signature[0];
|
|
||||||
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
|
|
||||||
var columnOneElements = archetype.Components[columnIndexOne].Elements;
|
|
||||||
|
|
||||||
var componentIdTwo = signature[1];
|
|
||||||
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
|
|
||||||
var columnTwoElements = archetype.Components[columnIndexTwo].Elements;
|
|
||||||
|
|
||||||
for (int i = archetype.Count - 1; i >= 0; i -= 1)
|
|
||||||
{
|
{
|
||||||
rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
|
var componentIdOne = archetype.Signature[0];
|
||||||
}
|
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
|
||||||
|
var columnOneElements = archetype.Components[columnIndexOne].Elements;
|
||||||
|
|
||||||
foreach (var edge in archetype.Edges.Values)
|
var componentIdTwo = archetype.Signature[1];
|
||||||
{
|
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
|
||||||
if (edge.Add != archetype)
|
var columnTwoElements = archetype.Components[columnIndexTwo].Elements;
|
||||||
|
|
||||||
|
for (int i = archetype.Count - 1; i >= 0; i -= 1)
|
||||||
{
|
{
|
||||||
ForEachEntity(edge.Add.Signature, rowAction);
|
rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ForEachEntity(ArchetypeSignature signature, Action<EntityId> rowAction)
|
|
||||||
{
|
|
||||||
var archetype = ArchetypeIndex[signature];
|
|
||||||
|
|
||||||
for (int i = 0; i < archetype.Count; i += 1)
|
|
||||||
{
|
|
||||||
rowAction(archetype.RowToEntity[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursion might get too hairy here
|
|
||||||
foreach (var edge in archetype.Edges.Values)
|
|
||||||
{
|
|
||||||
if (edge.Add != archetype)
|
|
||||||
{
|
|
||||||
ForEachEntity(edge.Add.Signature, rowAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReverseSpanEnumerator<EntityId> Entities(ArchetypeSignature signature)
|
|
||||||
{
|
|
||||||
var archetype = ArchetypeIndex[signature];
|
|
||||||
return new ReverseSpanEnumerator<EntityId>(
|
|
||||||
CollectionsMarshal.AsSpan(archetype.RowToEntity));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
|
|
Loading…
Reference in New Issue