unity
This commit is contained in:
parent
cfaefc4454
commit
6a86e8c267
34
.gitignore
vendored
34
.gitignore
vendored
@ -117,29 +117,11 @@ nuget/*.unitypackage
|
||||
|
||||
# Unity
|
||||
|
||||
src/MessagePipe.Unity/Library/*
|
||||
src/MessagePipe.Unity/Temp/*
|
||||
src/MessagePipe.Unity/Logs/*
|
||||
src/MessagePipe.Unity/Assembly-CSharp.csproj
|
||||
|
||||
src/MessagePipe.Unity/MessagePipe.csproj
|
||||
|
||||
src/MessagePipe.Unity/MessagePipe.Unity.sln
|
||||
|
||||
src/MessagePipe.Unity/MessagePipe.VContainer.csproj
|
||||
|
||||
src/MessagePipe.Unity/RuntimeUnitTestToolkit.csproj
|
||||
|
||||
src/MessagePipe.Unity/Tests.csproj
|
||||
|
||||
src/MessagePipe.Unity/Assembly-CSharp-firstpass.csproj
|
||||
|
||||
src/MessagePipe.Unity/MessagePipe.Zenject.csproj
|
||||
|
||||
src/MessagePipe.Unity/Assembly-CSharp-Editor.csproj
|
||||
|
||||
src/MessagePipe.Unity/Zenject-Editor.csproj
|
||||
|
||||
src/MessagePipe.Unity/Zenject.csproj
|
||||
|
||||
src/MessagePipe.Unity/MessagePipe.Editor.csproj
|
||||
src/ObservableCollections.Unity/Library/*
|
||||
src/ObservableCollections.Unity/Temp/*
|
||||
src/ObservableCollections.Unity/Logs/*
|
||||
src/ObservableCollections.Unity/Assembly-CSharp.csproj
|
||||
src/ObservableCollections.Unity/ObservableCollections.csproj
|
||||
src/ObservableCollections.Unity/ObservableCollections.Unity.sln
|
||||
src/ObservableCollections.Unity/Assembly-CSharp-firstpass.csproj
|
||||
src/ObservableCollections.Unity/Assembly-CSharp-Editor.csproj
|
8
src/ObservableCollections.Unity/Assets/Plugins.meta
Normal file
8
src/ObservableCollections.Unity/Assets/Plugins.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58b896faeb4c95c4a939b19f8dbf74ab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 791bdc9996d0f5c42bea2aa68c95dced
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2ca402f41ce60d4c95ee34b6715ee8a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed class FreezedDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>, IFreezedCollection<KeyValuePair<TKey, TValue>>
|
||||
|
||||
{
|
||||
readonly IReadOnlyDictionary<TKey, TValue> dictionary;
|
||||
|
||||
public FreezedDictionary(IReadOnlyDictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
|
||||
public TValue this[TKey key] => dictionary[key];
|
||||
|
||||
public IEnumerable<TKey> Keys => dictionary.Keys;
|
||||
|
||||
public IEnumerable<TValue> Values => dictionary.Values;
|
||||
|
||||
public int Count => dictionary.Count;
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new FreezedView<KeyValuePair<TKey, TValue>, TView>(dictionary, transform, reverse);
|
||||
}
|
||||
|
||||
public ISortableSynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateSortableView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform)
|
||||
{
|
||||
return new FreezedSortableView<KeyValuePair<TKey, TValue>, TView>(dictionary, transform);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1470201cf9f7db249b7f441e515b77b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,61 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed class FreezedList<T> : IReadOnlyList<T>, IFreezedCollection<T>
|
||||
{
|
||||
readonly IReadOnlyList<T> list;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public FreezedList(IReadOnlyList<T> list)
|
||||
{
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new FreezedView<T, TView>(list, transform, reverse);
|
||||
}
|
||||
|
||||
public ISortableSynchronizedView<T, TView> CreateSortableView<TView>(Func<T, TView> transform)
|
||||
{
|
||||
return new FreezedSortableView<T, TView>(list, transform);
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return list.Contains(item);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3207eb016a325514b8f137b4cf17df40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,118 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public delegate void NotifyCollectionChangedEventHandler<T>(in NotifyCollectionChangedEventArgs<T> e);
|
||||
|
||||
public interface IObservableCollection<T> : IReadOnlyCollection<T>
|
||||
{
|
||||
event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
object SyncRoot { get; }
|
||||
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false);
|
||||
}
|
||||
|
||||
public interface IFreezedCollection<T>
|
||||
{
|
||||
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false);
|
||||
ISortableSynchronizedView<T, TView> CreateSortableView<TView>(Func<T, TView> transform);
|
||||
}
|
||||
|
||||
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<(T Value, TView View)>, IDisposable
|
||||
{
|
||||
object SyncRoot { get; }
|
||||
|
||||
event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
void AttachFilter(ISynchronizedViewFilter<T, TView> filter);
|
||||
void ResetFilter(Action<T, TView> resetAction);
|
||||
INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged();
|
||||
}
|
||||
|
||||
public interface ISortableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
void Sort(IComparer<T> comparer);
|
||||
void Sort(IComparer<TView> viewComparer);
|
||||
}
|
||||
|
||||
// will be implemented in the future?
|
||||
//public interface IGroupedSynchoronizedView<T, TKey, TView> : ILookup<TKey, (T, TView)>, ISynchronizedView<T, TView>
|
||||
//{
|
||||
//}
|
||||
|
||||
public interface INotifyCollectionChangedSynchronizedView<T, TView> : ISynchronizedView<T, TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
}
|
||||
|
||||
public static class ObservableCollectionsExtensions
|
||||
{
|
||||
public static ISynchronizedView<T, TView> CreateSortedView<T, TKey, TView>(this IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||
|
||||
{
|
||||
return new SortedView<T, TKey, TView>(source, identitySelector, transform, comparer);
|
||||
}
|
||||
|
||||
public static ISynchronizedView<T, TView> CreateSortedView<T, TKey, TView>(this IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> viewComparer)
|
||||
|
||||
{
|
||||
return new SortedViewViewComparer<T, TKey, TView>(source, identitySelector, transform, viewComparer);
|
||||
}
|
||||
|
||||
public static ISynchronizedView<T, TView> CreateSortedView<T, TKey, TView, TCompare>(this IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, Func<T, TCompare> compareSelector, bool ascending = true)
|
||||
|
||||
{
|
||||
return source.CreateSortedView(identitySelector, transform, new AnonymousComparer<T, TCompare>(compareSelector, ascending));
|
||||
}
|
||||
|
||||
public static ISortableSynchronizedView<T, TView> CreateSortableView<T, TView>(this IFreezedCollection<T> source, Func<T, TView> transform, IComparer<T> initialSort)
|
||||
{
|
||||
var view = source.CreateSortableView(transform);
|
||||
view.Sort(initialSort);
|
||||
return view;
|
||||
}
|
||||
|
||||
public static ISortableSynchronizedView<T, TView> CreateSortableView<T, TView>(this IFreezedCollection<T> source, Func<T, TView> transform, IComparer<TView> initialViewSort)
|
||||
{
|
||||
var view = source.CreateSortableView(transform);
|
||||
view.Sort(initialViewSort);
|
||||
return view;
|
||||
}
|
||||
|
||||
public static ISortableSynchronizedView<T, TView> CreateSortableView<T, TView, TCompare>(this IFreezedCollection<T> source, Func<T, TView> transform, Func<T, TCompare> initialCompareSelector, bool ascending = true)
|
||||
{
|
||||
var view = source.CreateSortableView(transform);
|
||||
view.Sort(initialCompareSelector, ascending);
|
||||
return view;
|
||||
}
|
||||
|
||||
public static void Sort<T, TView, TCompare>(this ISortableSynchronizedView<T, TView> source, Func<T, TCompare> compareSelector, bool ascending = true)
|
||||
{
|
||||
source.Sort(new AnonymousComparer<T, TCompare>(compareSelector, ascending));
|
||||
}
|
||||
|
||||
class AnonymousComparer<T, TCompare> : IComparer<T>
|
||||
{
|
||||
readonly Func<T, TCompare> selector;
|
||||
readonly int f;
|
||||
|
||||
public AnonymousComparer(Func<T, TCompare> selector, bool ascending)
|
||||
{
|
||||
this.selector = selector;
|
||||
this.f = ascending ? 1 : -1;
|
||||
}
|
||||
|
||||
public int Compare(T x, T y)
|
||||
{
|
||||
if (x == null && y == null) return 0;
|
||||
if (x == null) return 1 * f;
|
||||
if (y == null) return -1 * f;
|
||||
|
||||
return Comparer<TCompare>.Default.Compare(selector(x), selector(y)) * f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdb4cc0f6a8b396418c8250eeaf2d2c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public interface ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
bool IsMatch(T value, TView view);
|
||||
void WhenTrue(T value, TView view);
|
||||
void WhenFalse(T value, TView view);
|
||||
void OnCollectionChanged(ChangedKind changedKind, T value, TView view);
|
||||
}
|
||||
|
||||
public enum ChangedKind
|
||||
{
|
||||
Add, Remove, Move
|
||||
}
|
||||
|
||||
public class SynchronizedViewFilter<T, TView> : ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public static readonly ISynchronizedViewFilter<T, TView> Null = new NullViewFilter();
|
||||
|
||||
readonly Func<T, TView, bool> isMatch;
|
||||
readonly Action<T, TView> whenTrue;
|
||||
readonly Action<T, TView> whenFalse;
|
||||
readonly Action<ChangedKind, T, TView> onCollectionChanged;
|
||||
|
||||
public SynchronizedViewFilter(Func<T, TView, bool> isMatch, Action<T, TView> whenTrue, Action<T, TView> whenFalse, Action<ChangedKind, T, TView> onCollectionChanged)
|
||||
{
|
||||
this.isMatch = isMatch;
|
||||
this.whenTrue = whenTrue;
|
||||
this.whenFalse = whenFalse;
|
||||
this.onCollectionChanged = onCollectionChanged;
|
||||
}
|
||||
|
||||
public bool IsMatch(T value, TView view) => isMatch(value, view);
|
||||
public void WhenFalse(T value, TView view) => whenFalse?.Invoke(value, view);
|
||||
public void WhenTrue(T value, TView view) => whenTrue?.Invoke(value, view);
|
||||
public void OnCollectionChanged(ChangedKind changedKind, T value, TView view) => onCollectionChanged?.Invoke(changedKind, value, view);
|
||||
|
||||
class NullViewFilter : ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public bool IsMatch(T value, TView view) => true;
|
||||
public void WhenFalse(T value, TView view) { }
|
||||
public void WhenTrue(T value, TView view) { }
|
||||
public void OnCollectionChanged(ChangedKind changedKind, T value, TView view) { }
|
||||
}
|
||||
}
|
||||
|
||||
public static class SynchronizedViewFilterExtensions
|
||||
{
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> filter)
|
||||
{
|
||||
source.AttachFilter(new SynchronizedViewFilter<T, TView>(filter, null, null, null));
|
||||
}
|
||||
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> isMatch, Action<T, TView> whenTrue, Action<T, TView> whenFalse)
|
||||
{
|
||||
source.AttachFilter(new SynchronizedViewFilter<T, TView>(isMatch, whenTrue, whenFalse, null));
|
||||
}
|
||||
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> isMatch, Action<T, TView> whenTrue, Action<T, TView> whenFalse, Action<ChangedKind, T, TView> onCollectionChanged)
|
||||
{
|
||||
source.AttachFilter(new SynchronizedViewFilter<T, TView>(isMatch, whenTrue, whenFalse, onCollectionChanged));
|
||||
}
|
||||
|
||||
public static bool IsNullFilter<T, TView>(this ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
return filter == SynchronizedViewFilter<T, TView>.Null;
|
||||
}
|
||||
|
||||
internal static void InvokeOnAdd<T, TView>(this ISynchronizedViewFilter<T, TView> filter, (T value, TView view) value)
|
||||
{
|
||||
InvokeOnAdd(filter, value.value, value.view);
|
||||
}
|
||||
|
||||
internal static void InvokeOnAdd<T, TView>(this ISynchronizedViewFilter<T, TView> filter, T value, TView view)
|
||||
{
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filter.WhenTrue(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.WhenFalse(value, view);
|
||||
}
|
||||
filter.OnCollectionChanged(ChangedKind.Add, value, view);
|
||||
}
|
||||
|
||||
internal static void InvokeOnRemove<T, TView>(this ISynchronizedViewFilter<T, TView> filter, (T value, TView view) value)
|
||||
{
|
||||
InvokeOnRemove(filter, value.value, value.view);
|
||||
}
|
||||
|
||||
internal static void InvokeOnRemove<T, TView>(this ISynchronizedViewFilter<T, TView> filter, T value, TView view)
|
||||
{
|
||||
filter.OnCollectionChanged(ChangedKind.Remove, value, view);
|
||||
}
|
||||
|
||||
internal static void InvokeOnMove<T, TView>(this ISynchronizedViewFilter<T, TView> filter, (T value, TView view) value)
|
||||
{
|
||||
InvokeOnMove(filter, value.value, value.view);
|
||||
}
|
||||
|
||||
internal static void InvokeOnMove<T, TView>(this ISynchronizedViewFilter<T, TView> filter, T value, TView view)
|
||||
{
|
||||
filter.OnCollectionChanged(ChangedKind.Move, value, view);
|
||||
}
|
||||
|
||||
internal static void InvokeOnAttach<T, TView>(this ISynchronizedViewFilter<T, TView> filter, T value, TView view)
|
||||
{
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filter.WhenTrue(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.WhenFalse(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ffdcef914adf8848a485ac600cc41c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b3e01975c6d488409ce0ac753c89870
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// ReadOnly cloned collection.
|
||||
/// </summary>
|
||||
internal struct CloneCollection<T> : IDisposable
|
||||
{
|
||||
T[] array;
|
||||
int length;
|
||||
|
||||
public ReadOnlySpan<T> Span => array.AsSpan(0, length);
|
||||
|
||||
public IEnumerable<T> AsEnumerable() => new EnumerableCollection(array, length);
|
||||
|
||||
public CloneCollection(T item)
|
||||
{
|
||||
this.array = ArrayPool<T>.Shared.Rent(1);
|
||||
this.length = 1;
|
||||
}
|
||||
|
||||
public CloneCollection(IEnumerable<T> source)
|
||||
{
|
||||
if (source.TryGetNonEnumeratedCount(out var count))
|
||||
{
|
||||
var array = ArrayPool<T>.Shared.Rent(count);
|
||||
|
||||
if (source is ICollection<T> c)
|
||||
{
|
||||
c.CopyTo(array, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var item in source)
|
||||
{
|
||||
array[i++] = item;
|
||||
}
|
||||
}
|
||||
this.array = array;
|
||||
this.length = count;
|
||||
}
|
||||
else
|
||||
{
|
||||
var array = ArrayPool<T>.Shared.Rent(count);
|
||||
|
||||
var i = 0;
|
||||
foreach (var item in source)
|
||||
{
|
||||
TryEnsureCapacity(ref array, i);
|
||||
array[i++] = item;
|
||||
}
|
||||
this.array = array;
|
||||
this.length = i;
|
||||
}
|
||||
}
|
||||
|
||||
public CloneCollection(ReadOnlySpan<T> source)
|
||||
{
|
||||
var array = ArrayPool<T>.Shared.Rent(source.Length);
|
||||
source.CopyTo(array);
|
||||
this.array = array;
|
||||
this.length = source.Length;
|
||||
}
|
||||
|
||||
static void TryEnsureCapacity(ref T[] array, int index)
|
||||
{
|
||||
if (array.Length == index)
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
|
||||
}
|
||||
array = ArrayPool<T>.Shared.Rent(index * 2);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (array != null)
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
|
||||
array = null!;
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize to use Count and CopyTo
|
||||
class EnumerableCollection : ICollection<T>
|
||||
{
|
||||
readonly T[] array;
|
||||
readonly int count;
|
||||
|
||||
public EnumerableCollection(T[] array, int count)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
this.array = Array.Empty<T>();
|
||||
this.count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.array = array;
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public void Add(T item) => throw new NotSupportedException();
|
||||
public void Clear() => throw new NotSupportedException();
|
||||
public bool Contains(T item) => throw new NotSupportedException();
|
||||
public void CopyTo(T[] dest, int destIndex) => array.CopyTo(dest, destIndex);
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
yield return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item) => throw new NotSupportedException();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b9643f0fa1bf7447961693f16cb557b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,205 @@
|
||||
#pragma warning disable CS0067
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal sealed class FreezedView<T, TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly bool reverse;
|
||||
readonly List<(T, TView)> list;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public FreezedView(IEnumerable<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.reverse = reverse;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.list = source.Select(x => (x, selector(x))).ToList();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in list)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (item, view) in list)
|
||||
{
|
||||
resetAction(item, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
if (!reverse)
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.GetEnumerator(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.AsEnumerable().Reverse().GetEnumerator(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FreezedSortableView<T, TView> : ISortableSynchronizedView<T, TView>
|
||||
{
|
||||
readonly (T, TView)[] array;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public FreezedSortableView(IEnumerable<T> source, Func<T, TView> selector)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.array = source.Select(x => (x, selector(x))).ToArray();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return array.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in array)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (item, view) in array)
|
||||
{
|
||||
resetAction(item, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, array.AsEnumerable().GetEnumerator(), filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Sort(IComparer<T> comparer)
|
||||
{
|
||||
Array.Sort(array, new TComparer(comparer));
|
||||
}
|
||||
|
||||
public void Sort(IComparer<TView> viewComparer)
|
||||
{
|
||||
Array.Sort(array, new TViewComparer(viewComparer));
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
|
||||
class TComparer : IComparer<(T, TView)>
|
||||
{
|
||||
readonly IComparer<T> comparer;
|
||||
|
||||
public TComparer(IComparer<T> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((T, TView) x, (T, TView) y)
|
||||
{
|
||||
return comparer.Compare(x.Item1, y.Item1);
|
||||
}
|
||||
}
|
||||
|
||||
class TViewComparer : IComparer<(T, TView)>
|
||||
{
|
||||
readonly IComparer<TView> comparer;
|
||||
|
||||
public TViewComparer(IComparer<TView> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((T, TView) x, (T, TView) y)
|
||||
{
|
||||
return comparer.Compare(x.Item2, y.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a768cdaf951b40345b6d5b21ade3e282
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class NotifyCollectionChangedSynchronizedView<T, TView> : INotifyCollectionChangedSynchronizedView<T, TView>
|
||||
{
|
||||
readonly ISynchronizedView<T, TView> parent;
|
||||
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new PropertyChangedEventArgs("Count");
|
||||
|
||||
public NotifyCollectionChangedSynchronizedView(ISynchronizedView<T, TView> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.parent.RoutingCollectionChanged += Parent_RoutingCollectionChanged;
|
||||
}
|
||||
|
||||
private void Parent_RoutingCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, e.ToStandardEventArgs());
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
// add, remove, reset will change the count.
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
PropertyChanged?.Invoke(this, CountPropertyChangedEventArgs);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public object SyncRoot => parent.SyncRoot;
|
||||
|
||||
public int Count => parent.Count;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged
|
||||
{
|
||||
add { parent.CollectionStateChanged += value; }
|
||||
remove { parent.CollectionStateChanged -= value; }
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged
|
||||
{
|
||||
add { parent.RoutingCollectionChanged += value; }
|
||||
remove { parent.RoutingCollectionChanged -= value; }
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter) => parent.AttachFilter(filter);
|
||||
public void ResetFilter(Action<T, TView> resetAction) => parent.ResetFilter(resetAction);
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged() => this;
|
||||
public void Dispose()
|
||||
{
|
||||
this.parent.RoutingCollectionChanged -= Parent_RoutingCollectionChanged;
|
||||
parent.Dispose();
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator() => parent.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => parent.GetEnumerator();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0e2d983b8f809a45b60f610d0542761
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
// internal ref struct ResizableArray<T>
|
||||
internal struct ResizableArray<T> : IDisposable
|
||||
{
|
||||
T[] array;
|
||||
int count;
|
||||
|
||||
public ReadOnlySpan<T> Span => array.AsSpan(0, count);
|
||||
|
||||
public ResizableArray(int initialCapacity)
|
||||
{
|
||||
array = ArrayPool<T>.Shared.Rent(initialCapacity);
|
||||
count = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
{
|
||||
if (array == null) Throw();
|
||||
if (array.Length == count)
|
||||
{
|
||||
EnsureCapacity();
|
||||
}
|
||||
array[count++] = item;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
void EnsureCapacity()
|
||||
{
|
||||
var newArray = array.AsSpan().ToArray();
|
||||
ArrayPool<T>.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
|
||||
array = newArray;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (array != null)
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
|
||||
array = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
void Throw()
|
||||
{
|
||||
throw new ObjectDisposedException("ResizableArray");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1477839cc1e36a4187e91e4fd001dd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SortedView<T, TKey, TView> : ISynchronizedView<T, TView>
|
||||
|
||||
{
|
||||
readonly IObservableCollection<T> source;
|
||||
readonly Func<T, TView> transform;
|
||||
readonly Func<T, TKey> identitySelector;
|
||||
readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> dict;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public SortedView(IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.identitySelector = identitySelector;
|
||||
this.transform = transform;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer));
|
||||
foreach (var v in source)
|
||||
{
|
||||
dict.Add((v, identitySelector(v)), (v, transform(v)));
|
||||
}
|
||||
|
||||
this.dict = dict;
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dict.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (_, (value, view)) in dict)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (_, (value, view)) in dict)
|
||||
{
|
||||
resetAction(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, dict.Select(x => x.Value).GetEnumerator(), filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
// Add, Insert
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((value, id), (value, view));
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.NewItems)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((value, id), (value, view));
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var id = identitySelector(value);
|
||||
dict.Remove((value, id), out var v);
|
||||
filter.InvokeOnRemove(v.Value, v.View);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.OldItems)
|
||||
{
|
||||
var id = identitySelector(value);
|
||||
dict.Remove((value, id), out var v);
|
||||
filter.InvokeOnRemove(v.Value, v.View);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// ReplaceRange is not supported in all ObservableCollections collections
|
||||
// Replace is remove old item and insert new item.
|
||||
{
|
||||
var oldValue = e.OldItem;
|
||||
dict.Remove((oldValue, identitySelector(oldValue)), out var oldView);
|
||||
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((value, id), (value, view));
|
||||
|
||||
filter.InvokeOnRemove(oldView);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
{
|
||||
// Move(index change) does not affect sorted list.
|
||||
var oldValue = e.OldItem;
|
||||
if (dict.TryGetValue((oldValue, identitySelector(oldValue)), out var view))
|
||||
{
|
||||
filter.InvokeOnMove(view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
dict.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Comparer : IComparer<(T value, TKey id)>
|
||||
{
|
||||
readonly IComparer<T> comparer;
|
||||
|
||||
public Comparer(IComparer<T> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((T value, TKey id) x, (T value, TKey id) y)
|
||||
{
|
||||
var compare = comparer.Compare(x.value, y.value);
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 768c43ec998d9754d8cc13398ed69d39
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SortedViewViewComparer<T, TKey, TView> : ISynchronizedView<T, TView>
|
||||
|
||||
{
|
||||
readonly IObservableCollection<T> source;
|
||||
readonly Func<T, TView> transform;
|
||||
readonly Func<T, TKey> identitySelector;
|
||||
readonly Dictionary<TKey, TView> viewMap; // view-map needs to use in remove.
|
||||
readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> list;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public SortedViewViewComparer(IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.identitySelector = identitySelector;
|
||||
this.transform = transform;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer));
|
||||
this.viewMap = new Dictionary<TKey, TView>();
|
||||
foreach (var value in source)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
}
|
||||
this.list = dict;
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (_, (value, view)) in list)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (_, (value, view)) in list)
|
||||
{
|
||||
resetAction(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
// Add, Insert
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.NewItems)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var id = identitySelector(value);
|
||||
if (viewMap.Remove(id, out var view))
|
||||
{
|
||||
list.Remove((view, id), out var v);
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.OldItems)
|
||||
{
|
||||
var id = identitySelector(value);
|
||||
if (viewMap.Remove(id, out var view))
|
||||
{
|
||||
list.Remove((view, id), out var v);
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// Replace is remove old item and insert new item.
|
||||
{
|
||||
var oldValue = e.OldItem;
|
||||
var oldKey = identitySelector(oldValue);
|
||||
if (viewMap.Remove(oldKey, out var oldView))
|
||||
{
|
||||
list.Remove((oldView, oldKey));
|
||||
filter.InvokeOnRemove(oldValue, oldView);
|
||||
}
|
||||
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
// Move(index change) does not affect soreted dict.
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var key = identitySelector(value);
|
||||
if (viewMap.TryGetValue(key, out var view))
|
||||
{
|
||||
filter.InvokeOnMove(value, view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
list.Clear();
|
||||
viewMap.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Comparer : IComparer<(TView view, TKey id)>
|
||||
{
|
||||
readonly IComparer<TView> comparer;
|
||||
|
||||
public Comparer(IComparer<TView> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((TView view, TKey id) x, (TView view, TKey id) y)
|
||||
{
|
||||
var compare = comparer.Compare(x.view, y.view);
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08e8703f31991094aa9216bf7166a549
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SynchronizedEnumerator<T> : IEnumerator<T>
|
||||
{
|
||||
bool isDisposed;
|
||||
readonly object gate;
|
||||
readonly bool lockTaken;
|
||||
readonly IEnumerator<T> enumerator;
|
||||
|
||||
public SynchronizedEnumerator(object gate, IEnumerator<T> enumerator)
|
||||
{
|
||||
this.gate = gate;
|
||||
this.enumerator = enumerator;
|
||||
Monitor.Enter(gate, ref lockTaken);
|
||||
}
|
||||
|
||||
public T Current => enumerator.Current;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
public bool MoveNext() => enumerator.MoveNext();
|
||||
public void Reset() => enumerator.Reset();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
isDisposed = true;
|
||||
try
|
||||
{
|
||||
enumerator.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
{
|
||||
Monitor.Exit(gate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08405416bc7c7e04e9bcaa4fa13c875f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SynchronizedViewEnumerator<T, TView> : IEnumerator<(T, TView)>, IDisposable
|
||||
{
|
||||
bool isDisposed;
|
||||
readonly bool lockTaken;
|
||||
readonly object gate;
|
||||
readonly IEnumerator<(T, TView)> enumerator;
|
||||
readonly ISynchronizedViewFilter<T, TView> filter;
|
||||
(T, TView) current;
|
||||
|
||||
public SynchronizedViewEnumerator(object gate, IEnumerator<(T, TView)> enumerator, ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
this.gate = gate;
|
||||
this.enumerator = enumerator;
|
||||
this.filter = filter;
|
||||
this.current = default;
|
||||
this.isDisposed = false;
|
||||
Monitor.Enter(gate, ref lockTaken);
|
||||
}
|
||||
|
||||
public (T, TView) Current => current;
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
current = enumerator.Current;
|
||||
if (filter.IsMatch(current.Item1, current.Item2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void Reset() => throw new NotSupportedException();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
isDisposed = true;
|
||||
try
|
||||
{
|
||||
enumerator.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
{
|
||||
Monitor.Exit(gate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98be7e821cfba26469f0911671fb24b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract:
|
||||
/// IsSingleItem ? (NewItem, OldItem) : (NewItems, OldItems)
|
||||
/// Action.Add
|
||||
/// NewItem, NewItems, NewStartingIndex
|
||||
/// Action.Remove
|
||||
/// OldItem, OldItems, OldStartingIndex
|
||||
/// Action.Replace
|
||||
/// NewItem, NewItems, OldItem, OldItems, (NewStartingIndex, OldStartingIndex = samevalue)
|
||||
/// Action.Move
|
||||
/// NewStartingIndex, OldStartingIndex
|
||||
/// Action.Reset
|
||||
/// -
|
||||
/// </summary>
|
||||
public readonly ref struct NotifyCollectionChangedEventArgs<T>
|
||||
{
|
||||
public readonly NotifyCollectionChangedAction Action;
|
||||
public readonly bool IsSingleItem;
|
||||
public readonly T NewItem;
|
||||
public readonly T OldItem;
|
||||
public readonly ReadOnlySpan<T> NewItems;
|
||||
public readonly ReadOnlySpan<T> OldItems;
|
||||
public readonly int NewStartingIndex;
|
||||
public readonly int OldStartingIndex;
|
||||
|
||||
public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, bool isSingleItem, T newItem = default, T oldItem = default, ReadOnlySpan<T> newItems = default, ReadOnlySpan<T> oldItems = default, int newStartingIndex = -1, int oldStartingIndex = -1)
|
||||
{
|
||||
Action = action;
|
||||
IsSingleItem = isSingleItem;
|
||||
NewItem = newItem;
|
||||
OldItem = oldItem;
|
||||
NewItems = newItems;
|
||||
OldItems = oldItems;
|
||||
NewStartingIndex = newStartingIndex;
|
||||
OldStartingIndex = oldStartingIndex;
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedEventArgs ToStandardEventArgs()
|
||||
{
|
||||
switch (Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (IsSingleItem)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, NewItem, NewStartingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, NewItems.ToArray(), NewStartingIndex);
|
||||
}
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (IsSingleItem)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, OldItem, OldStartingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, OldItems.ToArray(), OldStartingIndex);
|
||||
}
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
if (IsSingleItem)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, NewItem, OldItem, NewStartingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, NewItems.ToArray(), OldItems.ToArray(), NewStartingIndex);
|
||||
}
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs(Action, OldItem, NewStartingIndex, OldStartingIndex);
|
||||
}
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
return new NotifyCollectionChangedEventArgs(Action);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Add(T newItem, int newStartingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Add, true, newItem: newItem, newStartingIndex: newStartingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Add(ReadOnlySpan<T> newItems, int newStartingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Add, false, newItems: newItems, newStartingIndex: newStartingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Remove(T oldItem, int oldStartingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Remove, true, oldItem: oldItem, oldStartingIndex: oldStartingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Remove(ReadOnlySpan<T> oldItems, int oldStartingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Remove, false, oldItems: oldItems, oldStartingIndex: oldStartingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Replace(T newItem, T oldItem, int startingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Replace, true, newItem: newItem, oldItem: oldItem, newStartingIndex: startingIndex, oldStartingIndex: startingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Replace(ReadOnlySpan<T> newItems, ReadOnlySpan<T> oldItems, int startingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Replace, false, newItems: newItems, oldItems: oldItems, newStartingIndex: startingIndex, oldStartingIndex: startingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Move(T changedItem, int newStartingIndex, int oldStartingIndex)
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Move, true, oldItem: changedItem, newItem: changedItem, newStartingIndex: newStartingIndex, oldStartingIndex: oldStartingIndex);
|
||||
}
|
||||
|
||||
public static NotifyCollectionChangedEventArgs<T> Reset()
|
||||
{
|
||||
return new NotifyCollectionChangedEventArgs<T>(NotifyCollectionChangedAction.Reset, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70f96ba473572df4d91f125c2663b868
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,174 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableDictionary<TKey, TValue>
|
||||
{
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform, bool _ = false)
|
||||
{
|
||||
// reverse is no used.
|
||||
return new View<TView>(this, transform);
|
||||
}
|
||||
|
||||
// using key implicitly
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateSortedView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform, IComparer<KeyValuePair<TKey, TValue>> comparer)
|
||||
{
|
||||
return this.CreateSortedView(x => x.Key, transform, comparer);
|
||||
}
|
||||
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateSortedView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform, IComparer<TView> viewComparer)
|
||||
{
|
||||
return this.CreateSortedView(x => x.Key, transform, viewComparer);
|
||||
}
|
||||
|
||||
class View<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
||||
{
|
||||
readonly ObservableDictionary<TKey, TValue> source;
|
||||
readonly Func<KeyValuePair<TKey, TValue>, TView> selector;
|
||||
ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter;
|
||||
readonly Dictionary<TKey, (TValue, TView)> dict;
|
||||
|
||||
public View(ObservableDictionary<TKey, TValue> source, Func<KeyValuePair<TKey, TValue>, TView> selector)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.dict = source.dictionary.ToDictionary(x => x.Key, x => (x.Value, selector(x)));
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public object SyncRoot { get; }
|
||||
public event NotifyCollectionChangedEventHandler<KeyValuePair<TKey, TValue>> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dict.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var v in dict)
|
||||
{
|
||||
filter.InvokeOnAttach(new KeyValuePair<TKey, TValue>(v.Key, v.Value.Item1), v.Value.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<KeyValuePair<TKey, TValue>, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var v in dict)
|
||||
{
|
||||
resetAction(new KeyValuePair<TKey, TValue>(v.Key, v.Value.Item1), v.Value.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(KeyValuePair<TKey, TValue>, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<KeyValuePair<TKey, TValue>, TView>(SyncRoot,
|
||||
dict.Select(x => (new KeyValuePair<TKey, TValue>(x.Key, x.Value.Item1), x.Value.Item2)).GetEnumerator(),
|
||||
filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>> e)
|
||||
{
|
||||
// ObservableDictionary only provides single item operation and does not use int index.
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
var v = selector(e.NewItem);
|
||||
dict.Add(e.NewItem.Key, (e.NewItem.Value, v));
|
||||
filter.InvokeOnAdd(new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value), v);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (dict.Remove(e.OldItem.Key, out var v))
|
||||
{
|
||||
filter.InvokeOnRemove((new KeyValuePair<TKey, TValue>(e.OldItem.Key, v.Item1), v.Item2));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
if (dict.Remove(e.OldItem.Key, out var oldView))
|
||||
{
|
||||
filter.InvokeOnRemove((new KeyValuePair<TKey, TValue>(e.OldItem.Key, oldView.Item1), oldView.Item2));
|
||||
}
|
||||
|
||||
var v = selector(e.NewItem);
|
||||
dict[e.NewItem.Key] = (e.NewItem.Value, v);
|
||||
filter.InvokeOnAdd(new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value), v);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
{
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
filter.InvokeOnRemove((new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2));
|
||||
}
|
||||
}
|
||||
|
||||
dict.Clear();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation.
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f512d4ba320de58428976eb614bfa06d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,222 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableDictionary<TKey, TValue>
|
||||
: IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>>
|
||||
|
||||
{
|
||||
readonly Dictionary<TKey, TValue> dictionary;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ObservableDictionary()
|
||||
{
|
||||
this.dictionary = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
|
||||
public ObservableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
this.dictionary = new Dictionary<TKey, TValue>(collection);
|
||||
#else
|
||||
this.dictionary = new Dictionary<TKey, TValue>();
|
||||
foreach (var item in collection)
|
||||
{
|
||||
dictionary.Add(item.Key, item.Value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<KeyValuePair<TKey, TValue>> CollectionChanged;
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary[key];
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var oldValue))
|
||||
{
|
||||
dictionary[key] = value;
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Replace(
|
||||
new KeyValuePair<TKey, TValue>(key, value),
|
||||
new KeyValuePair<TKey, TValue>(key, oldValue),
|
||||
-1));
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for lock synchronization, hide keys and values.
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary.Keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary.Keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
dictionary.Add(key, value);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Add(new KeyValuePair<TKey, TValue>(key, value), -1));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
dictionary.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return ((IDictionary<TKey, TValue>)dictionary).ContainsKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (dictionary.Remove(key, out var value))
|
||||
{
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Remove(new KeyValuePair<TKey, TValue>(key, value), -1));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (dictionary.TryGetValue(item.Key, out var value))
|
||||
{
|
||||
if (EqualityComparer<TValue>.Default.Equals(value, item.Value))
|
||||
{
|
||||
if (dictionary.Remove(item.Key, out var value2))
|
||||
{
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Remove(new KeyValuePair<TKey, TValue>(item.Key, value2), -1));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8767
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
#pragma warning restore CS8767
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<KeyValuePair<TKey, TValue>>(SyncRoot, dictionary.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4dc0886473cf8c49947c9081454c614
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,296 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableFixedSizeRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly RingBuffer<T> buffer;
|
||||
readonly int capacity;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
|
||||
public ObservableFixedSizeRingBuffer(int capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
this.buffer = new RingBuffer<T>(capacity);
|
||||
}
|
||||
|
||||
public ObservableFixedSizeRingBuffer(int capacity, IEnumerable<T> collection)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
this.buffer = new RingBuffer<T>(capacity);
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (capacity == buffer.Count)
|
||||
{
|
||||
buffer.RemoveFirst();
|
||||
}
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return this.buffer[index];
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var oldValue = buffer[index];
|
||||
buffer[index] = value;
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Replace(value, oldValue, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return buffer.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFirst(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (capacity == buffer.Count)
|
||||
{
|
||||
var remItem = buffer.RemoveLast();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(remItem, capacity - 1));
|
||||
}
|
||||
|
||||
buffer.AddFirst(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLast(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (capacity == buffer.Count)
|
||||
{
|
||||
var remItem = buffer.RemoveFirst();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(remItem, 0));
|
||||
}
|
||||
|
||||
buffer.AddLast(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, buffer.Count - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public T RemoveFirst()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var item = buffer.RemoveFirst();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, 0));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public T RemoveLast()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = buffer.Count - 1;
|
||||
var item = buffer.RemoveLast();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, index));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// AddFirstRange is not exists.
|
||||
|
||||
public void AddLastRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
if (capacity <= buffer.Count + xs.Span.Length)
|
||||
{
|
||||
// calc remove count
|
||||
var remCount = Math.Min(buffer.Count, buffer.Count + xs.Span.Length - capacity);
|
||||
using (var ys = new ResizableArray<T>(remCount))
|
||||
{
|
||||
for (int i = 0; i < remCount; i++)
|
||||
{
|
||||
ys.Add(buffer.RemoveFirst());
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(ys.Span, 0));
|
||||
}
|
||||
}
|
||||
|
||||
var index = buffer.Count;
|
||||
var span = xs.Span;
|
||||
if (span.Length > capacity)
|
||||
{
|
||||
span = span.Slice(span.Length - capacity);
|
||||
}
|
||||
|
||||
foreach (var item in span)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLastRange(T[] items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (capacity <= buffer.Count + items.Length)
|
||||
{
|
||||
// calc remove count
|
||||
var remCount = Math.Min(buffer.Count, buffer.Count + items.Length - capacity);
|
||||
using (var ys = new ResizableArray<T>(remCount))
|
||||
{
|
||||
for (int i = 0; i < remCount; i++)
|
||||
{
|
||||
ys.Add(buffer.RemoveFirst());
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(ys.Span, 0));
|
||||
}
|
||||
}
|
||||
|
||||
var index = buffer.Count;
|
||||
var span = items.AsSpan();
|
||||
if (span.Length > capacity)
|
||||
{
|
||||
span = span.Slice(span.Length - capacity);
|
||||
}
|
||||
|
||||
foreach (var item in span)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(span, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLastRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (capacity <= buffer.Count + items.Length)
|
||||
{
|
||||
// calc remove count
|
||||
var remCount = Math.Min(buffer.Count, buffer.Count + items.Length - capacity);
|
||||
using (var ys = new ResizableArray<T>(remCount))
|
||||
{
|
||||
for (int i = 0; i < remCount; i++)
|
||||
{
|
||||
ys.Add(buffer.RemoveFirst());
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(ys.Span, 0));
|
||||
}
|
||||
}
|
||||
|
||||
var index = buffer.Count;
|
||||
var span = items;
|
||||
if (span.Length > capacity)
|
||||
{
|
||||
span = span.Slice(span.Length - capacity);
|
||||
}
|
||||
|
||||
foreach (var item in span)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(span, index));
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return buffer.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
AddLast(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
buffer.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return buffer.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
buffer.CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, buffer.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new ObservableRingBuffer<T>.View<TView>(this, transform, reverse);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81d799cc7eb02954d89c7ee52b7c9148
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,165 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool _ = false)
|
||||
{
|
||||
return new View<TView>(this, transform);
|
||||
}
|
||||
|
||||
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly ObservableHashSet<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly Dictionary<T, (T, TView)> dict;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(ObservableHashSet<T> source, Func<T, TView> selector)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.dict = source.set.ToDictionary(x => x, x => (x, selector(x)));
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dict.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (_, (value, view)) in dict)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (_, (value, view)) in dict)
|
||||
{
|
||||
resetAction(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, dict.Select(x => x.Value).GetEnumerator(), filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
dict.Add(e.NewItem, v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
dict.Add(item, v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
if (dict.Remove(e.OldItem, out var value))
|
||||
{
|
||||
filter.InvokeOnRemove(value.Item1, value.Item2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
if (dict.Remove(item, out var value))
|
||||
{
|
||||
filter.InvokeOnRemove(value.Item1, value.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
dict.Clear();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7b9e5ab06e68b242a4a722866654fd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,258 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
// can not implements ISet<T> because set operation can not get added/removed values.
|
||||
public sealed partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
|
||||
{
|
||||
readonly HashSet<T> set;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ObservableHashSet()
|
||||
{
|
||||
this.set = new HashSet<T>();
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
|
||||
|
||||
public ObservableHashSet(int capacity)
|
||||
{
|
||||
this.set = new HashSet<T>(capacity);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public ObservableHashSet(IEnumerable<T> collection)
|
||||
{
|
||||
this.set = new HashSet<T>(collection);
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool Add(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (set.Add(item))
|
||||
{
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, -1));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (!items.TryGetNonEnumeratedCount(out var capacity))
|
||||
{
|
||||
capacity = 4;
|
||||
}
|
||||
|
||||
using (var list = new ResizableArray<T>(capacity))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (set.Add(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(T[] items)
|
||||
{
|
||||
AddRange(items.AsSpan());
|
||||
}
|
||||
|
||||
public void AddRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var list = new ResizableArray<T>(items.Length))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (set.Add(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (set.Remove(item))
|
||||
{
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, -1));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (!items.TryGetNonEnumeratedCount(out var capacity))
|
||||
{
|
||||
capacity = 4;
|
||||
}
|
||||
|
||||
using (var list = new ResizableArray<T>(capacity))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (set.Remove(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(T[] items)
|
||||
{
|
||||
RemoveRange(items.AsSpan());
|
||||
}
|
||||
|
||||
public void RemoveRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var list = new ResizableArray<T>(items.Length))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (set.Remove(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
set.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD2_0 && !NET_STANDARD_2_0 && !NET_4_6
|
||||
|
||||
public bool TryGetValue(T equalValue, [MaybeNullWhen(false)] out T actualValue)
|
||||
{
|
||||
return set.TryGetValue(equalValue, out actualValue);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.IsProperSubsetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.IsProperSupersetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.IsSubsetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.IsSupersetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.Overlaps(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return set.SetEquals(other);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, set.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38d982a9907bb0047b158794a1cf35e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,220 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableList<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new View<TView>(this, transform, reverse);
|
||||
}
|
||||
|
||||
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly ObservableList<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
readonly List<(T, TView)> list;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(ObservableList<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.reverse = reverse;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.list = source.list.Select(x => (x, selector(x))).ToList();
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in list)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (item, view) in list)
|
||||
{
|
||||
resetAction(item, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
if (!reverse)
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.GetEnumerator(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.AsEnumerable().Reverse().GetEnumerator(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
// Add
|
||||
if (e.NewStartingIndex == list.Count)
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
list.Add(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
list.Add(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Insert
|
||||
else
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
list.Insert(e.NewStartingIndex, v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
// inefficient copy, need refactoring
|
||||
var newArray = new (T, TView)[e.NewItems.Length];
|
||||
var span = e.NewItems;
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
var v = (span[i], selector(span[i]));
|
||||
newArray[i] = v;
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
list.InsertRange(e.NewStartingIndex, newArray);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = list[e.OldStartingIndex];
|
||||
list.RemoveAt(e.OldStartingIndex);
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var len = e.OldStartingIndex + e.OldItems.Length;
|
||||
for (int i = e.OldStartingIndex; i < len; i++)
|
||||
{
|
||||
var v = list[i];
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
|
||||
list.RemoveRange(e.OldStartingIndex, e.OldItems.Length);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// ObservableList does not support replace range
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
|
||||
var oldItem = list[e.NewStartingIndex];
|
||||
list[e.NewStartingIndex] = v;
|
||||
|
||||
filter.InvokeOnRemove(oldItem);
|
||||
filter.InvokeOnAdd(v);
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
{
|
||||
var removeItem = list[e.OldStartingIndex];
|
||||
list.RemoveAt(e.OldStartingIndex);
|
||||
list.Insert(e.NewStartingIndex, removeItem);
|
||||
|
||||
filter.InvokeOnMove(removeItem);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
filter.InvokeOnRemove(item);
|
||||
}
|
||||
}
|
||||
list.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b98cf9d4d5426ad478ede70c5ac637d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,270 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableList<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly List<T> list;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ObservableList()
|
||||
{
|
||||
list = new List<T>();
|
||||
}
|
||||
|
||||
public ObservableList(int capacity)
|
||||
{
|
||||
list = new List<T>(capacity);
|
||||
}
|
||||
|
||||
public ObservableList(IEnumerable<T> collection)
|
||||
{
|
||||
list = collection.ToList();
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var oldValue = list[index];
|
||||
list[index] = value;
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Replace(value, oldValue, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = list.Count;
|
||||
list.Add(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = list.Count;
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
// to avoid iterate twice, require copy before insert.
|
||||
list.AddRange(xs.AsEnumerable());
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(T[] items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = list.Count;
|
||||
list.AddRange(items);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = list.Count;
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var l = new List<int>();
|
||||
lock (SyncRoot)
|
||||
{
|
||||
list.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, list.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void ForEach(Action<T> action)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
list.Insert(index, item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertRange(int index, T[] items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
list.InsertRange(index, items);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertRange(int index, IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
list.InsertRange(index, xs.AsEnumerable());
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertRange(int index, ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
list.InsertRange(index, xs.AsEnumerable());
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = list.IndexOf(item);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, index));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var item = list[index];
|
||||
list.RemoveAt(index);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(int index, int count)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
var range = CollectionsMarshal.AsSpan(list).Slice(index, count);
|
||||
#else
|
||||
var range = list.GetRange(index, count);
|
||||
#endif
|
||||
|
||||
// require copy before remove
|
||||
using (var xs = new CloneCollection<T>(range))
|
||||
{
|
||||
list.RemoveRange(index, count);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(xs.Span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Move(int oldIndex, int newIndex)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var removedItem = list[oldIndex];
|
||||
list.RemoveAt(oldIndex);
|
||||
list.Insert(newIndex, removedItem);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Move(removedItem, newIndex, oldIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 963681d5debafe6448e09cd9a4029cec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,173 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new View<TView>(this, transform, reverse);
|
||||
}
|
||||
|
||||
class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly ObservableQueue<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
protected readonly Queue<(T, TView)> queue;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(ObservableQueue<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.reverse = reverse;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.queue = new Queue<(T, TView)>(source.queue.Select(x => (x, selector(x))));
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in queue)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (item, view) in queue)
|
||||
{
|
||||
resetAction(item, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
if (!reverse)
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, queue.GetEnumerator(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, queue.AsEnumerable().Reverse().GetEnumerator(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
// Add(Enqueue, EnqueueRange)
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
queue.Enqueue(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
queue.Enqueue(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
// Dequeue, DequeuRange
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var len = e.OldItems.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in queue)
|
||||
{
|
||||
filter.InvokeOnRemove(item);
|
||||
}
|
||||
}
|
||||
queue.Clear();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fea76167e2a69749931bf01b5b5ed92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,214 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly Queue<T> queue;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ObservableQueue()
|
||||
{
|
||||
this.queue = new Queue<T>();
|
||||
}
|
||||
|
||||
public ObservableQueue(int capacity)
|
||||
{
|
||||
this.queue = new Queue<T>(capacity);
|
||||
}
|
||||
|
||||
public ObservableQueue(IEnumerable<T> collection)
|
||||
{
|
||||
this.queue = new Queue<T>(collection);
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = queue.Count;
|
||||
queue.Enqueue(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void EnqueueRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = queue.Count;
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
foreach (var item in xs.Span)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnqueueRange(T[] items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = queue.Count;
|
||||
foreach (var item in items)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void EnqueueRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = queue.Count;
|
||||
foreach (var item in items)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(v, 0));
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryDequeue([MaybeNullWhen(false)] out T result)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (queue.Count != 0)
|
||||
{
|
||||
result = queue.Dequeue();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(result, 0));
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void DequeueRange(int count)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var dest = ArrayPool<T>.Shared.Rent(count);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dest[i] = queue.Dequeue();
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest.AsSpan(0, count), 0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(dest, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DequeueRange(Span<T> dest)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
for (int i = 0; i < dest.Length; i++)
|
||||
{
|
||||
dest[i] = queue.Dequeue();
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
queue.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek([MaybeNullWhen(false)] T result)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (queue.Count != 0)
|
||||
{
|
||||
result = queue.Peek();
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
queue.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, queue.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb4558e36aaf5c84597551bb9a0d42e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,231 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableRingBuffer<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new View<TView>(this, transform, reverse);
|
||||
}
|
||||
|
||||
// used with ObservableFixedSizeRingBuffer
|
||||
internal sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly IObservableCollection<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
readonly RingBuffer<(T, TView)> ringBuffer;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(IObservableCollection<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.reverse = reverse;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.ringBuffer = new RingBuffer<(T, TView)>(source.Select(x => (x, selector(x))));
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return ringBuffer.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in ringBuffer)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (item, view) in ringBuffer)
|
||||
{
|
||||
resetAction(item, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
if (!reverse)
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, ringBuffer.AsEnumerable().GetEnumerator(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, ringBuffer.AsEnumerable().Reverse().GetEnumerator(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
// can not distinguish AddFirst and AddLast when collection count is 0.
|
||||
// So, in that case, use AddLast.
|
||||
// The internal structure may be different from the parent, but the result is same.
|
||||
// RangeOperation is only exists AddLastRange because we can not distinguish FirstRange or LastRange.
|
||||
if (e.NewStartingIndex == 0 && ringBuffer.Count != 0)
|
||||
{
|
||||
// AddFirst
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
ringBuffer.AddFirst(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
ringBuffer.AddFirst(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// AddLast
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
ringBuffer.AddLast(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
ringBuffer.AddLast(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
// starting from 0 is RemoveFirst
|
||||
if (e.OldStartingIndex == 0)
|
||||
{
|
||||
// RemoveFirst
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = ringBuffer.RemoveFirst();
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < e.OldItems.Length; i++)
|
||||
{
|
||||
var v = ringBuffer.RemoveFirst();
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RemoveLast
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = ringBuffer.RemoveLast();
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < e.OldItems.Length; i++)
|
||||
{
|
||||
var v = ringBuffer.RemoveLast();
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in ringBuffer)
|
||||
{
|
||||
filter.InvokeOnRemove(item);
|
||||
}
|
||||
}
|
||||
ringBuffer.Clear();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// range is not supported
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
|
||||
var oldItem = ringBuffer[e.NewStartingIndex];
|
||||
ringBuffer[e.NewStartingIndex] = v;
|
||||
|
||||
filter.InvokeOnRemove(oldItem);
|
||||
filter.InvokeOnAdd(v);
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a4b3308e0d0c4c4bafd65b41e7d66cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,206 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly RingBuffer<T> buffer;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
|
||||
public ObservableRingBuffer()
|
||||
{
|
||||
this.buffer = new RingBuffer<T>();
|
||||
}
|
||||
|
||||
public ObservableRingBuffer(IEnumerable<T> collection)
|
||||
{
|
||||
this.buffer = new RingBuffer<T>(collection);
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return this.buffer[index];
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var oldValue = buffer[index];
|
||||
buffer[index] = value;
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Replace(value, oldValue, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return buffer.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFirst(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
buffer.AddFirst(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLast(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, buffer.Count - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public T RemoveFirst()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var item = buffer.RemoveFirst();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, 0));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public T RemoveLast()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = buffer.Count - 1;
|
||||
var item = buffer.RemoveLast();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, index));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// AddFirstRange is not exists.
|
||||
|
||||
public void AddLastRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = buffer.Count;
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
foreach (var item in xs.Span)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLastRange(T[] items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = buffer.Count;
|
||||
foreach (var item in items)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLastRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = buffer.Count;
|
||||
foreach (var item in items)
|
||||
{
|
||||
buffer.AddLast(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return buffer.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
AddLast(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
buffer.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return buffer.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
buffer.CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, buffer.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c858015a9e82dc84487b3e627916a7ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,173 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
return new View<TView>(this, transform, reverse);
|
||||
}
|
||||
|
||||
class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly ObservableStack<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
protected readonly Stack<(T, TView)> stack;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction> CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(ObservableStack<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.reverse = reverse;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.stack = new Stack<(T, TView)>(source.stack.Select(x => (x, selector(x))));
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return stack.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in stack)
|
||||
{
|
||||
filter.InvokeOnAttach(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<T, TView> resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var (item, view) in stack)
|
||||
{
|
||||
resetAction(item, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator()
|
||||
{
|
||||
if (!reverse)
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, stack.GetEnumerator(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, stack.AsEnumerable().Reverse().GetEnumerator(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
// Add(Push, PushRange)
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
stack.Push(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
stack.Push(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
// Pop, PopRange
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = stack.Pop();
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var len = e.OldItems.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var v = stack.Pop();
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in stack)
|
||||
{
|
||||
filter.InvokeOnRemove(item);
|
||||
}
|
||||
}
|
||||
stack.Clear();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfc46ddd0f9e1e246931d87579f595b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,210 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly Stack<T> stack;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ObservableStack()
|
||||
{
|
||||
this.stack = new Stack<T>();
|
||||
}
|
||||
|
||||
public ObservableStack(int capacity)
|
||||
{
|
||||
this.stack = new Stack<T>(capacity);
|
||||
}
|
||||
|
||||
public ObservableStack(IEnumerable<T> collection)
|
||||
{
|
||||
this.stack = new Stack<T>(collection);
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T> CollectionChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return stack.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Push(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var index = stack.Count;
|
||||
stack.Push(item);
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
public void PushRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var xs = new CloneCollection<T>(items))
|
||||
{
|
||||
foreach (var item in xs.Span)
|
||||
{
|
||||
stack.Push(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void PushRange(T[] items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
stack.Push(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public void PushRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
stack.Push(item);
|
||||
}
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var v = stack.Pop();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(v, 0));
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPop([MaybeNullWhen(false)] out T result)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (stack.Count != 0)
|
||||
{
|
||||
result = stack.Pop();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(result, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void PopRange(int count)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
var dest = ArrayPool<T>.Shared.Rent(count);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dest[i] = stack.Pop();
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest.AsSpan(0, count), 0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(dest, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void PopRange(Span<T> dest)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
for (int i = 0; i < dest.Length; i++)
|
||||
{
|
||||
dest[i] = stack.Pop();
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
stack.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return stack.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek([MaybeNullWhen(false)] T result)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (stack.Count != 0)
|
||||
{
|
||||
result = stack.Peek();
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return stack.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
stack.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, stack.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e89128583a1ffee4babe861213e14be2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed class RingBuffer<T> : IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
T[] buffer;
|
||||
int head;
|
||||
int count;
|
||||
int mask;
|
||||
|
||||
public RingBuffer()
|
||||
{
|
||||
this.buffer = new T[8];
|
||||
this.head = 0;
|
||||
this.count = 0;
|
||||
this.mask = buffer.Length - 1;
|
||||
}
|
||||
|
||||
public RingBuffer(int capacity)
|
||||
{
|
||||
this.buffer = new T[CalculateCapacity(capacity)];
|
||||
this.head = 0;
|
||||
this.count = 0;
|
||||
this.mask = buffer.Length - 1;
|
||||
}
|
||||
|
||||
public RingBuffer(IEnumerable<T> collection)
|
||||
{
|
||||
var array = collection.TryGetNonEnumeratedCount(out var count)
|
||||
? new T[CalculateCapacity(count)]
|
||||
: new T[8];
|
||||
var i = 0;
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (i == array.Length)
|
||||
{
|
||||
Array.Resize(ref array, i * 2);
|
||||
}
|
||||
array[i++] = item;
|
||||
}
|
||||
|
||||
this.buffer = array;
|
||||
this.head = 0;
|
||||
this.count = i;
|
||||
this.mask = buffer.Length - 1;
|
||||
}
|
||||
|
||||
static int CalculateCapacity(int size)
|
||||
{
|
||||
size--;
|
||||
size |= size >> 1;
|
||||
size |= size >> 2;
|
||||
size |= size >> 4;
|
||||
size |= size >> 8;
|
||||
size |= size >> 16;
|
||||
size += 1;
|
||||
|
||||
if (size < 8)
|
||||
{
|
||||
size = 8;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var i = (head + index) & mask;
|
||||
return buffer[i];
|
||||
}
|
||||
set
|
||||
{
|
||||
var i = (head + index) & mask;
|
||||
buffer[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void AddLast(T item)
|
||||
{
|
||||
if (count == buffer.Length) EnsureCapacity();
|
||||
|
||||
var index = (head + count) & mask;
|
||||
buffer[index] = item;
|
||||
count++;
|
||||
}
|
||||
|
||||
public void AddFirst(T item)
|
||||
{
|
||||
if (count == buffer.Length) EnsureCapacity();
|
||||
|
||||
head = (head - 1) & mask;
|
||||
buffer[head] = item;
|
||||
count++;
|
||||
}
|
||||
|
||||
public T RemoveLast()
|
||||
{
|
||||
if (count == 0) ThrowForEmpty();
|
||||
|
||||
var index = (head + count - 1) & mask;
|
||||
var v = buffer[index];
|
||||
buffer[index] = default;
|
||||
count--;
|
||||
return v;
|
||||
}
|
||||
|
||||
public T RemoveFirst()
|
||||
{
|
||||
if (count == 0) ThrowForEmpty();
|
||||
|
||||
var index = head & mask;
|
||||
var v = buffer[index];
|
||||
buffer[index] = default;
|
||||
head = head + 1;
|
||||
count--;
|
||||
return v;
|
||||
}
|
||||
|
||||
void EnsureCapacity()
|
||||
{
|
||||
var newBuffer = new T[buffer.Length * 2];
|
||||
|
||||
var i = head & mask;
|
||||
buffer.AsSpan(i).CopyTo(newBuffer);
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
buffer.AsSpan(0, i).CopyTo(newBuffer.AsSpan(buffer.Length - i));
|
||||
}
|
||||
|
||||
head = 0;
|
||||
buffer = newBuffer;
|
||||
mask = newBuffer.Length - 1;
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
AddLast(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(buffer, 0, buffer.Length);
|
||||
head = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
public RingBufferSpan<T> GetSpan()
|
||||
{
|
||||
var start = head & mask;
|
||||
var end = (head + count) & mask;
|
||||
|
||||
if (end > start)
|
||||
{
|
||||
var first = buffer.AsSpan(start, count);
|
||||
var second = Array.Empty<T>().AsSpan();
|
||||
return new RingBufferSpan<T>(first, second, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
var first = buffer.AsSpan(start, buffer.Length - start);
|
||||
var second = buffer.AsSpan(0, end);
|
||||
return new RingBufferSpan<T>(first, second, count);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
if (count == 0) yield break;
|
||||
|
||||
var start = head & mask;
|
||||
var end = (head + count) & mask;
|
||||
|
||||
if (end > start)
|
||||
{
|
||||
// start...end
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
yield return buffer[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// start...
|
||||
for (int i = start; i < buffer.Length; i++)
|
||||
{
|
||||
yield return buffer[i];
|
||||
}
|
||||
// 0...end
|
||||
for (int i = 0; i < end; i++)
|
||||
{
|
||||
yield return buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Reverse()
|
||||
{
|
||||
var start = head & mask;
|
||||
var end = (head + count) & mask;
|
||||
|
||||
if (end > start)
|
||||
{
|
||||
// end...start
|
||||
for (int i = end - 1; i >= start; i--)
|
||||
{
|
||||
yield return buffer[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// end...0
|
||||
for (int i = end - 1; i >= 0; i--)
|
||||
{
|
||||
yield return buffer[i];
|
||||
}
|
||||
|
||||
// ...start
|
||||
for (int i = buffer.Length - 1; i >= start; i--)
|
||||
{
|
||||
yield return buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return IndexOf(item) != -1;
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
var span = GetSpan();
|
||||
var dest = array.AsSpan(arrayIndex);
|
||||
span.First.CopyTo(dest);
|
||||
span.Second.CopyTo(dest.Slice(span.First.Length));
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var v in GetSpan())
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(item, v))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
var result = new T[count];
|
||||
var i = 0;
|
||||
foreach (var item in GetSpan())
|
||||
{
|
||||
result[i++] = item;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)this).GetEnumerator();
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
static void ThrowForEmpty()
|
||||
{
|
||||
throw new InvalidOperationException("RingBuffer is empty.");
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct RingBufferSpan<T>
|
||||
{
|
||||
public readonly ReadOnlySpan<T> First;
|
||||
public readonly ReadOnlySpan<T> Second;
|
||||
public readonly int Count;
|
||||
|
||||
internal RingBufferSpan(ReadOnlySpan<T> first, ReadOnlySpan<T> second, int count)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
public ref struct Enumerator
|
||||
{
|
||||
ReadOnlySpan<T>.Enumerator firstEnumerator;
|
||||
ReadOnlySpan<T>.Enumerator secondEnumerator;
|
||||
bool useFirst;
|
||||
|
||||
public Enumerator(RingBufferSpan<T> span)
|
||||
{
|
||||
this.firstEnumerator = span.First.GetEnumerator();
|
||||
this.secondEnumerator = span.Second.GetEnumerator();
|
||||
this.useFirst = true;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (useFirst)
|
||||
{
|
||||
if (firstEnumerator.MoveNext())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
useFirst = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (secondEnumerator.MoveNext())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public T Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (useFirst)
|
||||
{
|
||||
return firstEnumerator.Current;
|
||||
}
|
||||
else
|
||||
{
|
||||
return secondEnumerator.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 090e1b8528f51164695f1ed11c685465
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c8f516891f9df44a847a0bb51a064f0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,64 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Collections.Generic
|
||||
{
|
||||
internal static class CollectionExtensions
|
||||
{
|
||||
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
|
||||
{
|
||||
key = kvp.Key;
|
||||
value = kvp.Value;
|
||||
}
|
||||
|
||||
public static bool Remove<TKey, TValue>(this SortedDictionary<TKey, TValue> dict, TKey key, out TValue value)
|
||||
{
|
||||
if (dict.TryGetValue(key, out value))
|
||||
{
|
||||
return dict.Remove(key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Remove<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, out TValue value)
|
||||
{
|
||||
if (dict.TryGetValue(key, out value))
|
||||
{
|
||||
return dict.Remove(key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !NET6_0_OR_GREATER
|
||||
|
||||
public static bool TryGetNonEnumeratedCount<T>(this IEnumerable<T> source, out int count)
|
||||
{
|
||||
if (source is ICollection<T> collection)
|
||||
{
|
||||
count = collection.Count;
|
||||
return true;
|
||||
}
|
||||
if (source is IReadOnlyCollection<T> rCollection)
|
||||
{
|
||||
count = rCollection.Count;
|
||||
return true;
|
||||
}
|
||||
count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !NET5_0_OR_GREATER
|
||||
|
||||
internal interface IReadOnlySet<T> : System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IReadOnlyCollection<T>
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02ab78c5678c3bd4ebb45ca99fa0eaea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
internal static class RuntimeHelpersEx
|
||||
{
|
||||
internal static bool IsReferenceOrContainsReferences<T>()
|
||||
{
|
||||
#if NETSTANDARD2_0 || NET_STANDARD_2_0 || NET_4_6
|
||||
return true;
|
||||
#else
|
||||
return RuntimeHelpers.IsReferenceOrContainsReferences<T>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90c6c97d9daed56458c4b0f69217c442
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
#if NETSTANDARD2_0 || NET_STANDARD_2_0 || NET_4_6
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
internal sealed class MaybeNullWhenAttribute : Attribute
|
||||
{
|
||||
public MaybeNullWhenAttribute(bool returnValue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoesNotReturnAttribute : Attribute
|
||||
{
|
||||
public DoesNotReturnAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b96ddbca917d2749a613d7dff847f37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80babde185fa5f5f58e1c7c451054bf5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00334fc99a0c324ce9667086377aec59
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9faef98abc153627f8af30123e43611c
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
src/ObservableCollections.Unity/Assets/Scenes.meta
Normal file
8
src/ObservableCollections.Unity/Assets/Scenes.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bc06841af72151429ea8e5bff9677a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
206
src/ObservableCollections.Unity/Assets/Scenes/SampleScene.unity
Normal file
206
src/ObservableCollections.Unity/Assets/Scenes/SampleScene.unity
Normal file
@ -0,0 +1,206 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 3
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
m_GIWorkflowMode: 1
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 0
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 0
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 500
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 500
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 2
|
||||
m_PVRDenoiserTypeDirect: 0
|
||||
m_PVRDenoiserTypeIndirect: 0
|
||||
m_PVRDenoiserTypeAO: 0
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 2
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
accuratePlacement: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &519420028
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 519420032}
|
||||
- component: {fileID: 519420031}
|
||||
- component: {fileID: 519420029}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &519420029
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &519420031
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 2
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_FocalLength: 50
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 1
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 0
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 0
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 0
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &519420032
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cda990e2423bbf4892e6590ba056729
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
43
src/ObservableCollections.Unity/Packages/manifest.json
Normal file
43
src/ObservableCollections.Unity/Packages/manifest.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.collab-proxy": "1.2.16",
|
||||
"com.unity.ext.nunit": "1.0.0",
|
||||
"com.unity.ide.rider": "1.1.0",
|
||||
"com.unity.ide.vscode": "1.1.0",
|
||||
"com.unity.test-framework": "1.1.1",
|
||||
"com.unity.textmeshpro": "2.0.1",
|
||||
"com.unity.timeline": "1.2.2",
|
||||
"com.unity.ugui": "1.0.0",
|
||||
"com.unity.modules.ai": "1.0.0",
|
||||
"com.unity.modules.androidjni": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.assetbundle": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.cloth": "1.0.0",
|
||||
"com.unity.modules.director": "1.0.0",
|
||||
"com.unity.modules.imageconversion": "1.0.0",
|
||||
"com.unity.modules.imgui": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0",
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.physics2d": "1.0.0",
|
||||
"com.unity.modules.screencapture": "1.0.0",
|
||||
"com.unity.modules.terrain": "1.0.0",
|
||||
"com.unity.modules.terrainphysics": "1.0.0",
|
||||
"com.unity.modules.tilemap": "1.0.0",
|
||||
"com.unity.modules.ui": "1.0.0",
|
||||
"com.unity.modules.uielements": "1.0.0",
|
||||
"com.unity.modules.umbra": "1.0.0",
|
||||
"com.unity.modules.unityanalytics": "1.0.0",
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestaudio": "1.0.0",
|
||||
"com.unity.modules.unitywebrequesttexture": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestwww": "1.0.0",
|
||||
"com.unity.modules.vehicles": "1.0.0",
|
||||
"com.unity.modules.video": "1.0.0",
|
||||
"com.unity.modules.vr": "1.0.0",
|
||||
"com.unity.modules.wind": "1.0.0",
|
||||
"com.unity.modules.xr": "1.0.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!11 &1
|
||||
AudioManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Volume: 1
|
||||
Rolloff Scale: 1
|
||||
Doppler Factor: 1
|
||||
Default Speaker Mode: 2
|
||||
m_SampleRate: 0
|
||||
m_DSPBufferSize: 1024
|
||||
m_VirtualVoiceCount: 512
|
||||
m_RealVoiceCount: 32
|
||||
m_SpatializerPlugin:
|
||||
m_AmbisonicDecoderPlugin:
|
||||
m_DisableAudio: 0
|
||||
m_VirtualizeEffects: 1
|
||||
m_RequestedDSPBufferSize: 0
|
@ -0,0 +1,6 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!236 &1
|
||||
ClusterInputManager:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Inputs: []
|
@ -0,0 +1,36 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!55 &1
|
||||
PhysicsManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_Gravity: {x: 0, y: -9.81, z: 0}
|
||||
m_DefaultMaterial: {fileID: 0}
|
||||
m_BounceThreshold: 2
|
||||
m_SleepThreshold: 0.005
|
||||
m_DefaultContactOffset: 0.01
|
||||
m_DefaultSolverIterations: 6
|
||||
m_DefaultSolverVelocityIterations: 1
|
||||
m_QueriesHitBackfaces: 0
|
||||
m_QueriesHitTriggers: 1
|
||||
m_EnableAdaptiveForce: 0
|
||||
m_ClothInterCollisionDistance: 0.1
|
||||
m_ClothInterCollisionStiffness: 0.2
|
||||
m_ContactsGeneration: 1
|
||||
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_AutoSimulation: 1
|
||||
m_AutoSyncTransforms: 0
|
||||
m_ReuseCollisionCallbacks: 1
|
||||
m_ClothInterCollisionSettingsToggle: 0
|
||||
m_ClothGravity: {x: 0, y: -9.81, z: 0}
|
||||
m_ContactPairsMode: 0
|
||||
m_BroadphaseType: 0
|
||||
m_WorldBounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 250, y: 250, z: 250}
|
||||
m_WorldSubdivisions: 8
|
||||
m_FrictionType: 0
|
||||
m_EnableEnhancedDeterminism: 0
|
||||
m_EnableUnifiedHeightmaps: 1
|
||||
m_SolverType: 0
|
||||
m_DefaultMaxAngularSpeed: 50
|
@ -0,0 +1,11 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1045 &1
|
||||
EditorBuildSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Scenes:
|
||||
- enabled: 1
|
||||
path: Assets/Scenes/SampleScene.unity
|
||||
guid: 2cda990e2423bbf4892e6590ba056729
|
||||
m_configObjects: {}
|
@ -0,0 +1,36 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!159 &1
|
||||
EditorSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_ExternalVersionControlSupport: Visible Meta Files
|
||||
m_SerializationMode: 2
|
||||
m_LineEndingsForNewScripts: 0
|
||||
m_DefaultBehaviorMode: 1
|
||||
m_PrefabRegularEnvironment: {fileID: 0}
|
||||
m_PrefabUIEnvironment: {fileID: 0}
|
||||
m_SpritePackerMode: 4
|
||||
m_SpritePackerPaddingPower: 1
|
||||
m_EtcTextureCompressorBehavior: 1
|
||||
m_EtcTextureFastCompressor: 1
|
||||
m_EtcTextureNormalCompressor: 2
|
||||
m_EtcTextureBestCompressor: 4
|
||||
m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;asmref;rsp
|
||||
m_ProjectGenerationRootNamespace:
|
||||
m_CollabEditorSettings:
|
||||
inProgressEnabled: 1
|
||||
m_EnableTextureStreamingInEditMode: 1
|
||||
m_EnableTextureStreamingInPlayMode: 1
|
||||
m_AsyncShaderCompilation: 1
|
||||
m_EnterPlayModeOptionsEnabled: 0
|
||||
m_EnterPlayModeOptions: 3
|
||||
m_ShowLightmapResolutionOverlay: 1
|
||||
m_UseLegacyProbeSampleCount: 0
|
||||
m_SerializeInlineMappingsOnOneLine: 1
|
||||
m_AssetPipelineMode: 1
|
||||
m_CacheServerMode: 0
|
||||
m_CacheServerEndpoint:
|
||||
m_CacheServerNamespacePrefix: default
|
||||
m_CacheServerEnableDownload: 1
|
||||
m_CacheServerEnableUpload: 1
|
@ -0,0 +1,63 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!30 &1
|
||||
GraphicsSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_Deferred:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_DeferredReflections:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_ScreenSpaceShadows:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LegacyDeferred:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_DepthNormals:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_MotionVectors:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightHalo:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LensFlare:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_VideoShadersIncludeMode: 2
|
||||
m_AlwaysIncludedShaders:
|
||||
- {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_PreloadedShaders: []
|
||||
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_CustomRenderPipeline: {fileID: 0}
|
||||
m_TransparencySortMode: 0
|
||||
m_TransparencySortAxis: {x: 0, y: 0, z: 1}
|
||||
m_DefaultRenderingPath: 1
|
||||
m_DefaultMobileRenderingPath: 1
|
||||
m_TierSettings: []
|
||||
m_LightmapStripping: 0
|
||||
m_FogStripping: 0
|
||||
m_InstancingStripping: 0
|
||||
m_LightmapKeepPlain: 1
|
||||
m_LightmapKeepDirCombined: 1
|
||||
m_LightmapKeepDynamicPlain: 1
|
||||
m_LightmapKeepDynamicDirCombined: 1
|
||||
m_LightmapKeepShadowMask: 1
|
||||
m_LightmapKeepSubtractive: 1
|
||||
m_FogKeepLinear: 1
|
||||
m_FogKeepExp: 1
|
||||
m_FogKeepExp2: 1
|
||||
m_AlbedoSwatchInfos: []
|
||||
m_LightsUseLinearIntensity: 0
|
||||
m_LightsUseColorTemperature: 0
|
||||
m_LogWhenShaderIsCompiled: 0
|
@ -0,0 +1,487 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!13 &1
|
||||
InputManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Axes:
|
||||
- serializedVersion: 3
|
||||
m_Name: Horizontal
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton: left
|
||||
positiveButton: right
|
||||
altNegativeButton: a
|
||||
altPositiveButton: d
|
||||
gravity: 3
|
||||
dead: 0.001
|
||||
sensitivity: 3
|
||||
snap: 1
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Vertical
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton: down
|
||||
positiveButton: up
|
||||
altNegativeButton: s
|
||||
altPositiveButton: w
|
||||
gravity: 3
|
||||
dead: 0.001
|
||||
sensitivity: 3
|
||||
snap: 1
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Fire1
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: left ctrl
|
||||
altNegativeButton:
|
||||
altPositiveButton: mouse 0
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Fire2
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: left alt
|
||||
altNegativeButton:
|
||||
altPositiveButton: mouse 1
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Fire3
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: left shift
|
||||
altNegativeButton:
|
||||
altPositiveButton: mouse 2
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Jump
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: space
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Mouse X
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton:
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0.1
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 1
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Mouse Y
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton:
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0.1
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 1
|
||||
axis: 1
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Mouse ScrollWheel
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton:
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0.1
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 1
|
||||
axis: 2
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Horizontal
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton:
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 0
|
||||
dead: 0.19
|
||||
sensitivity: 1
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 2
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Vertical
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton:
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 0
|
||||
dead: 0.19
|
||||
sensitivity: 1
|
||||
snap: 0
|
||||
invert: 1
|
||||
type: 2
|
||||
axis: 1
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Fire1
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: joystick button 0
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Fire2
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: joystick button 1
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Fire3
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: joystick button 2
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Jump
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: joystick button 3
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Submit
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: return
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 0
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Submit
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: enter
|
||||
altNegativeButton:
|
||||
altPositiveButton: space
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Cancel
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: escape
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 1
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Enable Debug Button 1
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: left ctrl
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 8
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Enable Debug Button 2
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: backspace
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 9
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Reset
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: left alt
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 1
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Next
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: page down
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 5
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Previous
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: page up
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 4
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Validate
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: return
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 0
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Persistent
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: right shift
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 2
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Multiplier
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton:
|
||||
positiveButton: left shift
|
||||
altNegativeButton:
|
||||
altPositiveButton: joystick button 3
|
||||
gravity: 0
|
||||
dead: 0
|
||||
sensitivity: 0
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Horizontal
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton: left
|
||||
positiveButton: right
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Vertical
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton: down
|
||||
positiveButton: up
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 0
|
||||
axis: 0
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Vertical
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton: down
|
||||
positiveButton: up
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 2
|
||||
axis: 6
|
||||
joyNum: 0
|
||||
- serializedVersion: 3
|
||||
m_Name: Debug Horizontal
|
||||
descriptiveName:
|
||||
descriptiveNegativeName:
|
||||
negativeButton: left
|
||||
positiveButton: right
|
||||
altNegativeButton:
|
||||
altPositiveButton:
|
||||
gravity: 1000
|
||||
dead: 0.001
|
||||
sensitivity: 1000
|
||||
snap: 0
|
||||
invert: 0
|
||||
type: 2
|
||||
axis: 5
|
||||
joyNum: 0
|
@ -0,0 +1,91 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!126 &1
|
||||
NavMeshProjectSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
areas:
|
||||
- name: Walkable
|
||||
cost: 1
|
||||
- name: Not Walkable
|
||||
cost: 1
|
||||
- name: Jump
|
||||
cost: 2
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
- name:
|
||||
cost: 1
|
||||
m_LastAgentTypeID: -887442657
|
||||
m_Settings:
|
||||
- serializedVersion: 2
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.75
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
accuratePlacement: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_SettingNames:
|
||||
- Humanoid
|
@ -0,0 +1,8 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!149 &1
|
||||
NetworkManager:
|
||||
m_ObjectHideFlags: 0
|
||||
m_DebugLevel: 0
|
||||
m_Sendrate: 15
|
||||
m_AssetToPrefab: {}
|
@ -0,0 +1,43 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &1
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 61
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_EnablePreviewPackages: 0
|
||||
m_EnablePackageDependencies: 0
|
||||
m_AdvancedSettingsExpanded: 1
|
||||
m_ScopedRegistriesSettingsExpanded: 1
|
||||
oneTimeWarningShown: 0
|
||||
m_Registries:
|
||||
- m_Id: main
|
||||
m_Name:
|
||||
m_Url: https://packages.unity.com
|
||||
m_Scopes: []
|
||||
m_IsDefault: 1
|
||||
m_Capabilities: 7
|
||||
m_UserSelectedRegistryName:
|
||||
m_UserAddingNewScopedRegistry: 0
|
||||
m_RegistryInfoDraft:
|
||||
m_ErrorMessage:
|
||||
m_Original:
|
||||
m_Id:
|
||||
m_Name:
|
||||
m_Url:
|
||||
m_Scopes: []
|
||||
m_IsDefault: 0
|
||||
m_Capabilities: 0
|
||||
m_Modified: 0
|
||||
m_Name:
|
||||
m_Url:
|
||||
m_Scopes:
|
||||
-
|
||||
m_SelectedScopeIndex: 0
|
@ -0,0 +1,56 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!19 &1
|
||||
Physics2DSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 5
|
||||
m_Gravity: {x: 0, y: -9.81}
|
||||
m_DefaultMaterial: {fileID: 0}
|
||||
m_VelocityIterations: 8
|
||||
m_PositionIterations: 3
|
||||
m_VelocityThreshold: 1
|
||||
m_MaxLinearCorrection: 0.2
|
||||
m_MaxAngularCorrection: 8
|
||||
m_MaxTranslationSpeed: 100
|
||||
m_MaxRotationSpeed: 360
|
||||
m_BaumgarteScale: 0.2
|
||||
m_BaumgarteTimeOfImpactScale: 0.75
|
||||
m_TimeToSleep: 0.5
|
||||
m_LinearSleepTolerance: 0.01
|
||||
m_AngularSleepTolerance: 2
|
||||
m_DefaultContactOffset: 0.01
|
||||
m_JobOptions:
|
||||
serializedVersion: 2
|
||||
useMultithreading: 0
|
||||
useConsistencySorting: 0
|
||||
m_InterpolationPosesPerJob: 100
|
||||
m_NewContactsPerJob: 30
|
||||
m_CollideContactsPerJob: 100
|
||||
m_ClearFlagsPerJob: 200
|
||||
m_ClearBodyForcesPerJob: 200
|
||||
m_SyncDiscreteFixturesPerJob: 50
|
||||
m_SyncContinuousFixturesPerJob: 50
|
||||
m_FindNearestContactsPerJob: 100
|
||||
m_UpdateTriggerContactsPerJob: 100
|
||||
m_IslandSolverCostThreshold: 100
|
||||
m_IslandSolverBodyCostScale: 1
|
||||
m_IslandSolverContactCostScale: 10
|
||||
m_IslandSolverJointCostScale: 10
|
||||
m_IslandSolverBodiesPerJob: 50
|
||||
m_IslandSolverContactsPerJob: 50
|
||||
m_SimulationMode: 0
|
||||
m_QueriesHitTriggers: 1
|
||||
m_QueriesStartInColliders: 1
|
||||
m_CallbacksOnDisable: 1
|
||||
m_ReuseCollisionCallbacks: 1
|
||||
m_AutoSyncTransforms: 0
|
||||
m_AlwaysShowColliders: 0
|
||||
m_ShowColliderSleep: 1
|
||||
m_ShowColliderContacts: 0
|
||||
m_ShowColliderAABB: 0
|
||||
m_ContactArrowScale: 0.2
|
||||
m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
|
||||
m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
|
||||
m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
|
||||
m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
|
||||
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
@ -0,0 +1,7 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1386491679 &1
|
||||
PresetManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_DefaultPresets: {}
|
@ -0,0 +1,646 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!129 &1
|
||||
PlayerSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 22
|
||||
productGUID: 4852b04bea0e36b428b29c34acc83ad9
|
||||
AndroidProfiler: 0
|
||||
AndroidFilterTouchesWhenObscured: 0
|
||||
AndroidEnableSustainedPerformanceMode: 0
|
||||
defaultScreenOrientation: 4
|
||||
targetDevice: 2
|
||||
useOnDemandResources: 0
|
||||
accelerometerFrequency: 60
|
||||
companyName: DefaultCompany
|
||||
productName: ObservableCollections.Unity
|
||||
defaultCursor: {fileID: 0}
|
||||
cursorHotspot: {x: 0, y: 0}
|
||||
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
|
||||
m_ShowUnitySplashScreen: 1
|
||||
m_ShowUnitySplashLogo: 1
|
||||
m_SplashScreenOverlayOpacity: 1
|
||||
m_SplashScreenAnimation: 1
|
||||
m_SplashScreenLogoStyle: 1
|
||||
m_SplashScreenDrawMode: 0
|
||||
m_SplashScreenBackgroundAnimationZoom: 1
|
||||
m_SplashScreenLogoAnimationZoom: 1
|
||||
m_SplashScreenBackgroundLandscapeAspect: 1
|
||||
m_SplashScreenBackgroundPortraitAspect: 1
|
||||
m_SplashScreenBackgroundLandscapeUvs:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
m_SplashScreenBackgroundPortraitUvs:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
m_SplashScreenLogos: []
|
||||
m_VirtualRealitySplashScreen: {fileID: 0}
|
||||
m_HolographicTrackingLossScreen: {fileID: 0}
|
||||
defaultScreenWidth: 1920
|
||||
defaultScreenHeight: 1080
|
||||
defaultScreenWidthWeb: 960
|
||||
defaultScreenHeightWeb: 600
|
||||
m_StereoRenderingPath: 0
|
||||
m_ActiveColorSpace: 0
|
||||
m_MTRendering: 1
|
||||
mipStripping: 0
|
||||
numberOfMipsStripped: 0
|
||||
m_StackTraceTypes: 010000000100000001000000010000000100000001000000
|
||||
iosShowActivityIndicatorOnLoading: -1
|
||||
androidShowActivityIndicatorOnLoading: -1
|
||||
iosUseCustomAppBackgroundBehavior: 0
|
||||
iosAllowHTTPDownload: 1
|
||||
allowedAutorotateToPortrait: 1
|
||||
allowedAutorotateToPortraitUpsideDown: 1
|
||||
allowedAutorotateToLandscapeRight: 1
|
||||
allowedAutorotateToLandscapeLeft: 1
|
||||
useOSAutorotation: 1
|
||||
use32BitDisplayBuffer: 1
|
||||
preserveFramebufferAlpha: 0
|
||||
disableDepthAndStencilBuffers: 0
|
||||
androidStartInFullscreen: 1
|
||||
androidRenderOutsideSafeArea: 1
|
||||
androidUseSwappy: 1
|
||||
androidBlitType: 0
|
||||
defaultIsNativeResolution: 1
|
||||
macRetinaSupport: 1
|
||||
runInBackground: 0
|
||||
captureSingleScreen: 0
|
||||
muteOtherAudioSources: 0
|
||||
Prepare IOS For Recording: 0
|
||||
Force IOS Speakers When Recording: 0
|
||||
deferSystemGesturesMode: 0
|
||||
hideHomeButton: 0
|
||||
submitAnalytics: 1
|
||||
usePlayerLog: 1
|
||||
bakeCollisionMeshes: 0
|
||||
forceSingleInstance: 0
|
||||
useFlipModelSwapchain: 1
|
||||
resizableWindow: 0
|
||||
useMacAppStoreValidation: 0
|
||||
macAppStoreCategory: public.app-category.games
|
||||
gpuSkinning: 0
|
||||
xboxPIXTextureCapture: 0
|
||||
xboxEnableAvatar: 0
|
||||
xboxEnableKinect: 0
|
||||
xboxEnableKinectAutoTracking: 0
|
||||
xboxEnableFitness: 0
|
||||
visibleInBackground: 1
|
||||
allowFullscreenSwitch: 1
|
||||
fullscreenMode: 1
|
||||
xboxSpeechDB: 0
|
||||
xboxEnableHeadOrientation: 0
|
||||
xboxEnableGuest: 0
|
||||
xboxEnablePIXSampling: 0
|
||||
metalFramebufferOnly: 0
|
||||
xboxOneResolution: 0
|
||||
xboxOneSResolution: 0
|
||||
xboxOneXResolution: 3
|
||||
xboxOneMonoLoggingLevel: 0
|
||||
xboxOneLoggingLevel: 1
|
||||
xboxOneDisableEsram: 0
|
||||
xboxOneEnableTypeOptimization: 0
|
||||
xboxOnePresentImmediateThreshold: 0
|
||||
switchQueueCommandMemory: 1048576
|
||||
switchQueueControlMemory: 16384
|
||||
switchQueueComputeMemory: 262144
|
||||
switchNVNShaderPoolsGranularity: 33554432
|
||||
switchNVNDefaultPoolsGranularity: 16777216
|
||||
switchNVNOtherPoolsGranularity: 16777216
|
||||
switchNVNMaxPublicTextureIDCount: 0
|
||||
switchNVNMaxPublicSamplerIDCount: 0
|
||||
stadiaPresentMode: 0
|
||||
stadiaTargetFramerate: 0
|
||||
vulkanNumSwapchainBuffers: 3
|
||||
vulkanEnableSetSRGBWrite: 0
|
||||
vulkanEnablePreTransform: 0
|
||||
vulkanEnableLateAcquireNextImage: 0
|
||||
m_SupportedAspectRatios:
|
||||
4:3: 1
|
||||
5:4: 1
|
||||
16:10: 1
|
||||
16:9: 1
|
||||
Others: 1
|
||||
bundleVersion: 1.0
|
||||
preloadedAssets: []
|
||||
metroInputSource: 0
|
||||
wsaTransparentSwapchain: 0
|
||||
m_HolographicPauseOnTrackingLoss: 1
|
||||
xboxOneDisableKinectGpuReservation: 1
|
||||
xboxOneEnable7thCore: 1
|
||||
vrSettings:
|
||||
enable360StereoCapture: 0
|
||||
isWsaHolographicRemotingEnabled: 0
|
||||
enableFrameTimingStats: 0
|
||||
useHDRDisplay: 0
|
||||
D3DHDRBitDepth: 0
|
||||
m_ColorGamuts: 00000000
|
||||
targetPixelDensity: 30
|
||||
resolutionScalingMode: 0
|
||||
androidSupportedAspectRatio: 1
|
||||
androidMaxAspectRatio: 2.1
|
||||
applicationIdentifier: {}
|
||||
buildNumber:
|
||||
Standalone: 0
|
||||
iPhone: 0
|
||||
tvOS: 0
|
||||
overrideDefaultApplicationIdentifier: 0
|
||||
AndroidBundleVersionCode: 1
|
||||
AndroidMinSdkVersion: 19
|
||||
AndroidTargetSdkVersion: 0
|
||||
AndroidPreferredInstallLocation: 1
|
||||
aotOptions:
|
||||
stripEngineCode: 1
|
||||
iPhoneStrippingLevel: 0
|
||||
iPhoneScriptCallOptimization: 0
|
||||
ForceInternetPermission: 0
|
||||
ForceSDCardPermission: 0
|
||||
CreateWallpaper: 0
|
||||
APKExpansionFiles: 0
|
||||
keepLoadedShadersAlive: 0
|
||||
StripUnusedMeshComponents: 0
|
||||
VertexChannelCompressionMask: 4054
|
||||
iPhoneSdkVersion: 988
|
||||
iOSTargetOSVersionString: 11.0
|
||||
tvOSSdkVersion: 0
|
||||
tvOSRequireExtendedGameController: 0
|
||||
tvOSTargetOSVersionString: 11.0
|
||||
uIPrerenderedIcon: 0
|
||||
uIRequiresPersistentWiFi: 0
|
||||
uIRequiresFullScreen: 1
|
||||
uIStatusBarHidden: 1
|
||||
uIExitOnSuspend: 0
|
||||
uIStatusBarStyle: 0
|
||||
appleTVSplashScreen: {fileID: 0}
|
||||
appleTVSplashScreen2x: {fileID: 0}
|
||||
tvOSSmallIconLayers: []
|
||||
tvOSSmallIconLayers2x: []
|
||||
tvOSLargeIconLayers: []
|
||||
tvOSLargeIconLayers2x: []
|
||||
tvOSTopShelfImageLayers: []
|
||||
tvOSTopShelfImageLayers2x: []
|
||||
tvOSTopShelfImageWideLayers: []
|
||||
tvOSTopShelfImageWideLayers2x: []
|
||||
iOSLaunchScreenType: 0
|
||||
iOSLaunchScreenPortrait: {fileID: 0}
|
||||
iOSLaunchScreenLandscape: {fileID: 0}
|
||||
iOSLaunchScreenBackgroundColor:
|
||||
serializedVersion: 2
|
||||
rgba: 0
|
||||
iOSLaunchScreenFillPct: 100
|
||||
iOSLaunchScreenSize: 100
|
||||
iOSLaunchScreenCustomXibPath:
|
||||
iOSLaunchScreeniPadType: 0
|
||||
iOSLaunchScreeniPadImage: {fileID: 0}
|
||||
iOSLaunchScreeniPadBackgroundColor:
|
||||
serializedVersion: 2
|
||||
rgba: 0
|
||||
iOSLaunchScreeniPadFillPct: 100
|
||||
iOSLaunchScreeniPadSize: 100
|
||||
iOSLaunchScreeniPadCustomXibPath:
|
||||
iOSLaunchScreenCustomStoryboardPath:
|
||||
iOSLaunchScreeniPadCustomStoryboardPath:
|
||||
iOSDeviceRequirements: []
|
||||
iOSURLSchemes: []
|
||||
iOSBackgroundModes: 0
|
||||
iOSMetalForceHardShadows: 0
|
||||
metalEditorSupport: 1
|
||||
metalAPIValidation: 1
|
||||
iOSRenderExtraFrameOnPause: 0
|
||||
iosCopyPluginsCodeInsteadOfSymlink: 0
|
||||
appleDeveloperTeamID:
|
||||
iOSManualSigningProvisioningProfileID:
|
||||
tvOSManualSigningProvisioningProfileID:
|
||||
iOSManualSigningProvisioningProfileType: 0
|
||||
tvOSManualSigningProvisioningProfileType: 0
|
||||
appleEnableAutomaticSigning: 0
|
||||
iOSRequireARKit: 0
|
||||
iOSAutomaticallyDetectAndAddCapabilities: 1
|
||||
appleEnableProMotion: 0
|
||||
shaderPrecisionModel: 0
|
||||
clonedFromGUID: 10ad67313f4034357812315f3c407484
|
||||
templatePackageId: com.unity.template.2d@5.0.0
|
||||
templateDefaultScene: Assets/Scenes/SampleScene.unity
|
||||
useCustomMainManifest: 0
|
||||
useCustomLauncherManifest: 0
|
||||
useCustomMainGradleTemplate: 0
|
||||
useCustomLauncherGradleManifest: 0
|
||||
useCustomBaseGradleTemplate: 0
|
||||
useCustomGradlePropertiesTemplate: 0
|
||||
useCustomProguardFile: 0
|
||||
AndroidTargetArchitectures: 1
|
||||
AndroidSplashScreenScale: 0
|
||||
androidSplashScreen: {fileID: 0}
|
||||
AndroidKeystoreName:
|
||||
AndroidKeyaliasName:
|
||||
AndroidBuildApkPerCpuArchitecture: 0
|
||||
AndroidTVCompatibility: 0
|
||||
AndroidIsGame: 1
|
||||
AndroidEnableTango: 0
|
||||
androidEnableBanner: 1
|
||||
androidUseLowAccuracyLocation: 0
|
||||
androidUseCustomKeystore: 0
|
||||
m_AndroidBanners:
|
||||
- width: 320
|
||||
height: 180
|
||||
banner: {fileID: 0}
|
||||
androidGamepadSupportLevel: 0
|
||||
AndroidMinifyWithR8: 0
|
||||
AndroidMinifyRelease: 0
|
||||
AndroidMinifyDebug: 0
|
||||
AndroidValidateAppBundleSize: 1
|
||||
AndroidAppBundleSizeToValidate: 150
|
||||
m_BuildTargetIcons: []
|
||||
m_BuildTargetPlatformIcons: []
|
||||
m_BuildTargetBatching: []
|
||||
m_BuildTargetGraphicsJobs:
|
||||
- m_BuildTarget: MacStandaloneSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: Switch
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: MetroSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: AppleTVSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: BJMSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: LinuxStandaloneSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: PS4Player
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: iOSSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: WindowsStandaloneSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: XboxOnePlayer
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: LuminSupport
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: AndroidPlayer
|
||||
m_GraphicsJobs: 0
|
||||
- m_BuildTarget: WebGLSupport
|
||||
m_GraphicsJobs: 0
|
||||
m_BuildTargetGraphicsJobMode: []
|
||||
m_BuildTargetGraphicsAPIs:
|
||||
- m_BuildTarget: AndroidPlayer
|
||||
m_APIs: 150000000b000000
|
||||
m_Automatic: 0
|
||||
- m_BuildTarget: iOSSupport
|
||||
m_APIs: 10000000
|
||||
m_Automatic: 1
|
||||
m_BuildTargetVRSettings: []
|
||||
openGLRequireES31: 0
|
||||
openGLRequireES31AEP: 0
|
||||
openGLRequireES32: 0
|
||||
m_TemplateCustomTags: {}
|
||||
mobileMTRendering:
|
||||
Android: 1
|
||||
iPhone: 1
|
||||
tvOS: 1
|
||||
m_BuildTargetGroupLightmapEncodingQuality: []
|
||||
m_BuildTargetGroupLightmapSettings: []
|
||||
m_BuildTargetNormalMapEncoding: []
|
||||
playModeTestRunnerEnabled: 0
|
||||
runPlayModeTestAsEditModeTest: 0
|
||||
actionOnDotNetUnhandledException: 1
|
||||
enableInternalProfiler: 0
|
||||
logObjCUncaughtExceptions: 1
|
||||
enableCrashReportAPI: 0
|
||||
cameraUsageDescription:
|
||||
locationUsageDescription:
|
||||
microphoneUsageDescription:
|
||||
switchNMETAOverride:
|
||||
switchNetLibKey:
|
||||
switchSocketMemoryPoolSize: 6144
|
||||
switchSocketAllocatorPoolSize: 128
|
||||
switchSocketConcurrencyLimit: 14
|
||||
switchScreenResolutionBehavior: 2
|
||||
switchUseCPUProfiler: 0
|
||||
switchUseGOLDLinker: 0
|
||||
switchApplicationID: 0x01004b9000490000
|
||||
switchNSODependencies:
|
||||
switchTitleNames_0:
|
||||
switchTitleNames_1:
|
||||
switchTitleNames_2:
|
||||
switchTitleNames_3:
|
||||
switchTitleNames_4:
|
||||
switchTitleNames_5:
|
||||
switchTitleNames_6:
|
||||
switchTitleNames_7:
|
||||
switchTitleNames_8:
|
||||
switchTitleNames_9:
|
||||
switchTitleNames_10:
|
||||
switchTitleNames_11:
|
||||
switchTitleNames_12:
|
||||
switchTitleNames_13:
|
||||
switchTitleNames_14:
|
||||
switchTitleNames_15:
|
||||
switchPublisherNames_0:
|
||||
switchPublisherNames_1:
|
||||
switchPublisherNames_2:
|
||||
switchPublisherNames_3:
|
||||
switchPublisherNames_4:
|
||||
switchPublisherNames_5:
|
||||
switchPublisherNames_6:
|
||||
switchPublisherNames_7:
|
||||
switchPublisherNames_8:
|
||||
switchPublisherNames_9:
|
||||
switchPublisherNames_10:
|
||||
switchPublisherNames_11:
|
||||
switchPublisherNames_12:
|
||||
switchPublisherNames_13:
|
||||
switchPublisherNames_14:
|
||||
switchPublisherNames_15:
|
||||
switchIcons_0: {fileID: 0}
|
||||
switchIcons_1: {fileID: 0}
|
||||
switchIcons_2: {fileID: 0}
|
||||
switchIcons_3: {fileID: 0}
|
||||
switchIcons_4: {fileID: 0}
|
||||
switchIcons_5: {fileID: 0}
|
||||
switchIcons_6: {fileID: 0}
|
||||
switchIcons_7: {fileID: 0}
|
||||
switchIcons_8: {fileID: 0}
|
||||
switchIcons_9: {fileID: 0}
|
||||
switchIcons_10: {fileID: 0}
|
||||
switchIcons_11: {fileID: 0}
|
||||
switchIcons_12: {fileID: 0}
|
||||
switchIcons_13: {fileID: 0}
|
||||
switchIcons_14: {fileID: 0}
|
||||
switchIcons_15: {fileID: 0}
|
||||
switchSmallIcons_0: {fileID: 0}
|
||||
switchSmallIcons_1: {fileID: 0}
|
||||
switchSmallIcons_2: {fileID: 0}
|
||||
switchSmallIcons_3: {fileID: 0}
|
||||
switchSmallIcons_4: {fileID: 0}
|
||||
switchSmallIcons_5: {fileID: 0}
|
||||
switchSmallIcons_6: {fileID: 0}
|
||||
switchSmallIcons_7: {fileID: 0}
|
||||
switchSmallIcons_8: {fileID: 0}
|
||||
switchSmallIcons_9: {fileID: 0}
|
||||
switchSmallIcons_10: {fileID: 0}
|
||||
switchSmallIcons_11: {fileID: 0}
|
||||
switchSmallIcons_12: {fileID: 0}
|
||||
switchSmallIcons_13: {fileID: 0}
|
||||
switchSmallIcons_14: {fileID: 0}
|
||||
switchSmallIcons_15: {fileID: 0}
|
||||
switchManualHTML:
|
||||
switchAccessibleURLs:
|
||||
switchLegalInformation:
|
||||
switchMainThreadStackSize: 1048576
|
||||
switchPresenceGroupId:
|
||||
switchLogoHandling: 0
|
||||
switchReleaseVersion: 0
|
||||
switchDisplayVersion: 1.0.0
|
||||
switchStartupUserAccount: 0
|
||||
switchTouchScreenUsage: 0
|
||||
switchSupportedLanguagesMask: 0
|
||||
switchLogoType: 0
|
||||
switchApplicationErrorCodeCategory:
|
||||
switchUserAccountSaveDataSize: 0
|
||||
switchUserAccountSaveDataJournalSize: 0
|
||||
switchApplicationAttribute: 0
|
||||
switchCardSpecSize: -1
|
||||
switchCardSpecClock: -1
|
||||
switchRatingsMask: 0
|
||||
switchRatingsInt_0: 0
|
||||
switchRatingsInt_1: 0
|
||||
switchRatingsInt_2: 0
|
||||
switchRatingsInt_3: 0
|
||||
switchRatingsInt_4: 0
|
||||
switchRatingsInt_5: 0
|
||||
switchRatingsInt_6: 0
|
||||
switchRatingsInt_7: 0
|
||||
switchRatingsInt_8: 0
|
||||
switchRatingsInt_9: 0
|
||||
switchRatingsInt_10: 0
|
||||
switchRatingsInt_11: 0
|
||||
switchRatingsInt_12: 0
|
||||
switchLocalCommunicationIds_0:
|
||||
switchLocalCommunicationIds_1:
|
||||
switchLocalCommunicationIds_2:
|
||||
switchLocalCommunicationIds_3:
|
||||
switchLocalCommunicationIds_4:
|
||||
switchLocalCommunicationIds_5:
|
||||
switchLocalCommunicationIds_6:
|
||||
switchLocalCommunicationIds_7:
|
||||
switchParentalControl: 0
|
||||
switchAllowsScreenshot: 1
|
||||
switchAllowsVideoCapturing: 1
|
||||
switchAllowsRuntimeAddOnContentInstall: 0
|
||||
switchDataLossConfirmation: 0
|
||||
switchUserAccountLockEnabled: 0
|
||||
switchSystemResourceMemory: 16777216
|
||||
switchSupportedNpadStyles: 22
|
||||
switchNativeFsCacheSize: 32
|
||||
switchIsHoldTypeHorizontal: 0
|
||||
switchSupportedNpadCount: 8
|
||||
switchSocketConfigEnabled: 0
|
||||
switchTcpInitialSendBufferSize: 32
|
||||
switchTcpInitialReceiveBufferSize: 64
|
||||
switchTcpAutoSendBufferSizeMax: 256
|
||||
switchTcpAutoReceiveBufferSizeMax: 256
|
||||
switchUdpSendBufferSize: 9
|
||||
switchUdpReceiveBufferSize: 42
|
||||
switchSocketBufferEfficiency: 4
|
||||
switchSocketInitializeEnabled: 1
|
||||
switchNetworkInterfaceManagerInitializeEnabled: 1
|
||||
switchPlayerConnectionEnabled: 1
|
||||
switchUseNewStyleFilepaths: 0
|
||||
switchUseMicroSleepForYield: 1
|
||||
switchMicroSleepForYieldTime: 25
|
||||
ps4NPAgeRating: 12
|
||||
ps4NPTitleSecret:
|
||||
ps4NPTrophyPackPath:
|
||||
ps4ParentalLevel: 11
|
||||
ps4ContentID: ED1633-NPXX51362_00-0000000000000000
|
||||
ps4Category: 0
|
||||
ps4MasterVersion: 01.00
|
||||
ps4AppVersion: 01.00
|
||||
ps4AppType: 0
|
||||
ps4ParamSfxPath:
|
||||
ps4VideoOutPixelFormat: 0
|
||||
ps4VideoOutInitialWidth: 1920
|
||||
ps4VideoOutBaseModeInitialWidth: 1920
|
||||
ps4VideoOutReprojectionRate: 60
|
||||
ps4PronunciationXMLPath:
|
||||
ps4PronunciationSIGPath:
|
||||
ps4BackgroundImagePath:
|
||||
ps4StartupImagePath:
|
||||
ps4StartupImagesFolder:
|
||||
ps4IconImagesFolder:
|
||||
ps4SaveDataImagePath:
|
||||
ps4SdkOverride:
|
||||
ps4BGMPath:
|
||||
ps4ShareFilePath:
|
||||
ps4ShareOverlayImagePath:
|
||||
ps4PrivacyGuardImagePath:
|
||||
ps4ExtraSceSysFile:
|
||||
ps4NPtitleDatPath:
|
||||
ps4RemotePlayKeyAssignment: -1
|
||||
ps4RemotePlayKeyMappingDir:
|
||||
ps4PlayTogetherPlayerCount: 0
|
||||
ps4EnterButtonAssignment: 2
|
||||
ps4ApplicationParam1: 0
|
||||
ps4ApplicationParam2: 0
|
||||
ps4ApplicationParam3: 0
|
||||
ps4ApplicationParam4: 0
|
||||
ps4DownloadDataSize: 0
|
||||
ps4GarlicHeapSize: 2048
|
||||
ps4ProGarlicHeapSize: 2560
|
||||
playerPrefsMaxSize: 32768
|
||||
ps4Passcode: bi9UOuSpM2Tlh01vOzwvSikHFswuzleh
|
||||
ps4pnSessions: 1
|
||||
ps4pnPresence: 1
|
||||
ps4pnFriends: 1
|
||||
ps4pnGameCustomData: 1
|
||||
playerPrefsSupport: 0
|
||||
enableApplicationExit: 0
|
||||
resetTempFolder: 1
|
||||
restrictedAudioUsageRights: 0
|
||||
ps4UseResolutionFallback: 0
|
||||
ps4ReprojectionSupport: 0
|
||||
ps4UseAudio3dBackend: 0
|
||||
ps4UseLowGarlicFragmentationMode: 1
|
||||
ps4SocialScreenEnabled: 0
|
||||
ps4ScriptOptimizationLevel: 2
|
||||
ps4Audio3dVirtualSpeakerCount: 14
|
||||
ps4attribCpuUsage: 0
|
||||
ps4PatchPkgPath:
|
||||
ps4PatchLatestPkgPath:
|
||||
ps4PatchChangeinfoPath:
|
||||
ps4PatchDayOne: 0
|
||||
ps4attribUserManagement: 0
|
||||
ps4attribMoveSupport: 0
|
||||
ps4attrib3DSupport: 0
|
||||
ps4attribShareSupport: 0
|
||||
ps4attribExclusiveVR: 0
|
||||
ps4disableAutoHideSplash: 0
|
||||
ps4videoRecordingFeaturesUsed: 0
|
||||
ps4contentSearchFeaturesUsed: 0
|
||||
ps4CompatibilityPS5: 0
|
||||
ps4GPU800MHz: 1
|
||||
ps4attribEyeToEyeDistanceSettingVR: 0
|
||||
ps4IncludedModules: []
|
||||
ps4attribVROutputEnabled: 0
|
||||
monoEnv:
|
||||
splashScreenBackgroundSourceLandscape: {fileID: 0}
|
||||
splashScreenBackgroundSourcePortrait: {fileID: 0}
|
||||
blurSplashScreenBackground: 1
|
||||
spritePackerPolicy:
|
||||
webGLMemorySize: 32
|
||||
webGLExceptionSupport: 1
|
||||
webGLNameFilesAsHashes: 0
|
||||
webGLDataCaching: 1
|
||||
webGLDebugSymbols: 0
|
||||
webGLEmscriptenArgs:
|
||||
webGLModulesDirectory:
|
||||
webGLTemplate: APPLICATION:Default
|
||||
webGLAnalyzeBuildSize: 0
|
||||
webGLUseEmbeddedResources: 0
|
||||
webGLCompressionFormat: 0
|
||||
webGLWasmArithmeticExceptions: 0
|
||||
webGLLinkerTarget: 1
|
||||
webGLThreadsSupport: 0
|
||||
webGLDecompressionFallback: 0
|
||||
scriptingDefineSymbols: {}
|
||||
additionalCompilerArguments: {}
|
||||
platformArchitecture: {}
|
||||
scriptingBackend: {}
|
||||
il2cppCompilerConfiguration: {}
|
||||
managedStrippingLevel: {}
|
||||
incrementalIl2cppBuild: {}
|
||||
suppressCommonWarnings: 1
|
||||
allowUnsafeCode: 0
|
||||
useDeterministicCompilation: 1
|
||||
useReferenceAssemblies: 1
|
||||
enableRoslynAnalyzers: 1
|
||||
additionalIl2CppArgs:
|
||||
scriptingRuntimeVersion: 1
|
||||
gcIncremental: 1
|
||||
assemblyVersionValidation: 1
|
||||
gcWBarrierValidation: 0
|
||||
apiCompatibilityLevelPerPlatform: {}
|
||||
m_RenderingPath: 1
|
||||
m_MobileRenderingPath: 1
|
||||
metroPackageName: 2D_BuiltInRenderer
|
||||
metroPackageVersion:
|
||||
metroCertificatePath:
|
||||
metroCertificatePassword:
|
||||
metroCertificateSubject:
|
||||
metroCertificateIssuer:
|
||||
metroCertificateNotAfter: 0000000000000000
|
||||
metroApplicationDescription: 2D_BuiltInRenderer
|
||||
wsaImages: {}
|
||||
metroTileShortName:
|
||||
metroTileShowName: 0
|
||||
metroMediumTileShowName: 0
|
||||
metroLargeTileShowName: 0
|
||||
metroWideTileShowName: 0
|
||||
metroSupportStreamingInstall: 0
|
||||
metroLastRequiredScene: 0
|
||||
metroDefaultTileSize: 1
|
||||
metroTileForegroundText: 2
|
||||
metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
|
||||
metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1}
|
||||
metroSplashScreenUseBackgroundColor: 0
|
||||
platformCapabilities: {}
|
||||
metroTargetDeviceFamilies: {}
|
||||
metroFTAName:
|
||||
metroFTAFileTypes: []
|
||||
metroProtocolName:
|
||||
XboxOneProductId:
|
||||
XboxOneUpdateKey:
|
||||
XboxOneSandboxId:
|
||||
XboxOneContentId:
|
||||
XboxOneTitleId:
|
||||
XboxOneSCId:
|
||||
XboxOneGameOsOverridePath:
|
||||
XboxOnePackagingOverridePath:
|
||||
XboxOneAppManifestOverridePath:
|
||||
XboxOneVersion: 1.0.0.0
|
||||
XboxOnePackageEncryption: 0
|
||||
XboxOnePackageUpdateGranularity: 2
|
||||
XboxOneDescription:
|
||||
XboxOneLanguage:
|
||||
- enus
|
||||
XboxOneCapability: []
|
||||
XboxOneGameRating: {}
|
||||
XboxOneIsContentPackage: 0
|
||||
XboxOneEnhancedXboxCompatibilityMode: 0
|
||||
XboxOneEnableGPUVariability: 1
|
||||
XboxOneSockets: {}
|
||||
XboxOneSplashScreen: {fileID: 0}
|
||||
XboxOneAllowedProductIds: []
|
||||
XboxOnePersistentLocalStorageSize: 0
|
||||
XboxOneXTitleMemory: 8
|
||||
XboxOneOverrideIdentityName:
|
||||
XboxOneOverrideIdentityPublisher:
|
||||
vrEditorSettings: {}
|
||||
cloudServicesEnabled: {}
|
||||
luminIcon:
|
||||
m_Name:
|
||||
m_ModelFolderPath:
|
||||
m_PortalFolderPath:
|
||||
luminCert:
|
||||
m_CertPath:
|
||||
m_SignPackage: 1
|
||||
luminIsChannelApp: 0
|
||||
luminVersion:
|
||||
m_VersionCode: 1
|
||||
m_VersionName:
|
||||
apiCompatibilityLevel: 6
|
||||
activeInputHandler: 0
|
||||
cloudProjectId:
|
||||
framebufferDepthMemorylessMode: 0
|
||||
qualitySettingsNames: []
|
||||
projectName:
|
||||
organizationId:
|
||||
cloudEnabled: 0
|
||||
legacyClampBlendShapeWeights: 0
|
||||
virtualTexturingSupportEnabled: 0
|
@ -0,0 +1,2 @@
|
||||
m_EditorVersion: 2020.1.0a3
|
||||
m_EditorVersionWithRevision: 2020.1.0a3 (a37e4d4d532f)
|
@ -0,0 +1,236 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!47 &1
|
||||
QualitySettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 5
|
||||
m_CurrentQuality: 5
|
||||
m_QualitySettings:
|
||||
- serializedVersion: 2
|
||||
name: Very Low
|
||||
pixelLightCount: 0
|
||||
shadows: 0
|
||||
shadowResolution: 0
|
||||
shadowProjection: 1
|
||||
shadowCascades: 1
|
||||
shadowDistance: 15
|
||||
shadowNearPlaneOffset: 3
|
||||
shadowCascade2Split: 0.33333334
|
||||
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||
shadowmaskMode: 0
|
||||
skinWeights: 1
|
||||
textureQuality: 1
|
||||
anisotropicTextures: 0
|
||||
antiAliasing: 0
|
||||
softParticles: 0
|
||||
softVegetation: 0
|
||||
realtimeReflectionProbes: 0
|
||||
billboardsFaceCameraPosition: 0
|
||||
vSyncCount: 0
|
||||
lodBias: 0.3
|
||||
maximumLODLevel: 0
|
||||
streamingMipmapsActive: 0
|
||||
streamingMipmapsAddAllCameras: 1
|
||||
streamingMipmapsMemoryBudget: 512
|
||||
streamingMipmapsRenderersPerFrame: 512
|
||||
streamingMipmapsMaxLevelReduction: 2
|
||||
streamingMipmapsMaxFileIORequests: 1024
|
||||
particleRaycastBudget: 4
|
||||
asyncUploadTimeSlice: 2
|
||||
asyncUploadBufferSize: 16
|
||||
asyncUploadPersistentBuffer: 1
|
||||
resolutionScalingFixedDPIFactor: 1
|
||||
customRenderPipeline: {fileID: 0}
|
||||
excludedTargetPlatforms: []
|
||||
- serializedVersion: 2
|
||||
name: Low
|
||||
pixelLightCount: 0
|
||||
shadows: 0
|
||||
shadowResolution: 0
|
||||
shadowProjection: 1
|
||||
shadowCascades: 1
|
||||
shadowDistance: 20
|
||||
shadowNearPlaneOffset: 3
|
||||
shadowCascade2Split: 0.33333334
|
||||
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||
shadowmaskMode: 0
|
||||
skinWeights: 2
|
||||
textureQuality: 0
|
||||
anisotropicTextures: 0
|
||||
antiAliasing: 0
|
||||
softParticles: 0
|
||||
softVegetation: 0
|
||||
realtimeReflectionProbes: 0
|
||||
billboardsFaceCameraPosition: 0
|
||||
vSyncCount: 0
|
||||
lodBias: 0.4
|
||||
maximumLODLevel: 0
|
||||
streamingMipmapsActive: 0
|
||||
streamingMipmapsAddAllCameras: 1
|
||||
streamingMipmapsMemoryBudget: 512
|
||||
streamingMipmapsRenderersPerFrame: 512
|
||||
streamingMipmapsMaxLevelReduction: 2
|
||||
streamingMipmapsMaxFileIORequests: 1024
|
||||
particleRaycastBudget: 16
|
||||
asyncUploadTimeSlice: 2
|
||||
asyncUploadBufferSize: 16
|
||||
asyncUploadPersistentBuffer: 1
|
||||
resolutionScalingFixedDPIFactor: 1
|
||||
customRenderPipeline: {fileID: 0}
|
||||
excludedTargetPlatforms: []
|
||||
- serializedVersion: 2
|
||||
name: Medium
|
||||
pixelLightCount: 1
|
||||
shadows: 1
|
||||
shadowResolution: 0
|
||||
shadowProjection: 1
|
||||
shadowCascades: 1
|
||||
shadowDistance: 20
|
||||
shadowNearPlaneOffset: 3
|
||||
shadowCascade2Split: 0.33333334
|
||||
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||
shadowmaskMode: 0
|
||||
skinWeights: 2
|
||||
textureQuality: 0
|
||||
anisotropicTextures: 1
|
||||
antiAliasing: 0
|
||||
softParticles: 0
|
||||
softVegetation: 0
|
||||
realtimeReflectionProbes: 0
|
||||
billboardsFaceCameraPosition: 0
|
||||
vSyncCount: 1
|
||||
lodBias: 0.7
|
||||
maximumLODLevel: 0
|
||||
streamingMipmapsActive: 0
|
||||
streamingMipmapsAddAllCameras: 1
|
||||
streamingMipmapsMemoryBudget: 512
|
||||
streamingMipmapsRenderersPerFrame: 512
|
||||
streamingMipmapsMaxLevelReduction: 2
|
||||
streamingMipmapsMaxFileIORequests: 1024
|
||||
particleRaycastBudget: 64
|
||||
asyncUploadTimeSlice: 2
|
||||
asyncUploadBufferSize: 16
|
||||
asyncUploadPersistentBuffer: 1
|
||||
resolutionScalingFixedDPIFactor: 1
|
||||
customRenderPipeline: {fileID: 0}
|
||||
excludedTargetPlatforms: []
|
||||
- serializedVersion: 2
|
||||
name: High
|
||||
pixelLightCount: 2
|
||||
shadows: 2
|
||||
shadowResolution: 1
|
||||
shadowProjection: 1
|
||||
shadowCascades: 2
|
||||
shadowDistance: 40
|
||||
shadowNearPlaneOffset: 3
|
||||
shadowCascade2Split: 0.33333334
|
||||
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||
shadowmaskMode: 1
|
||||
skinWeights: 2
|
||||
textureQuality: 0
|
||||
anisotropicTextures: 1
|
||||
antiAliasing: 0
|
||||
softParticles: 0
|
||||
softVegetation: 1
|
||||
realtimeReflectionProbes: 1
|
||||
billboardsFaceCameraPosition: 1
|
||||
vSyncCount: 1
|
||||
lodBias: 1
|
||||
maximumLODLevel: 0
|
||||
streamingMipmapsActive: 0
|
||||
streamingMipmapsAddAllCameras: 1
|
||||
streamingMipmapsMemoryBudget: 512
|
||||
streamingMipmapsRenderersPerFrame: 512
|
||||
streamingMipmapsMaxLevelReduction: 2
|
||||
streamingMipmapsMaxFileIORequests: 1024
|
||||
particleRaycastBudget: 256
|
||||
asyncUploadTimeSlice: 2
|
||||
asyncUploadBufferSize: 16
|
||||
asyncUploadPersistentBuffer: 1
|
||||
resolutionScalingFixedDPIFactor: 1
|
||||
customRenderPipeline: {fileID: 0}
|
||||
excludedTargetPlatforms: []
|
||||
- serializedVersion: 2
|
||||
name: Very High
|
||||
pixelLightCount: 3
|
||||
shadows: 2
|
||||
shadowResolution: 2
|
||||
shadowProjection: 1
|
||||
shadowCascades: 2
|
||||
shadowDistance: 70
|
||||
shadowNearPlaneOffset: 3
|
||||
shadowCascade2Split: 0.33333334
|
||||
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||
shadowmaskMode: 1
|
||||
skinWeights: 4
|
||||
textureQuality: 0
|
||||
anisotropicTextures: 2
|
||||
antiAliasing: 2
|
||||
softParticles: 1
|
||||
softVegetation: 1
|
||||
realtimeReflectionProbes: 1
|
||||
billboardsFaceCameraPosition: 1
|
||||
vSyncCount: 1
|
||||
lodBias: 1.5
|
||||
maximumLODLevel: 0
|
||||
streamingMipmapsActive: 0
|
||||
streamingMipmapsAddAllCameras: 1
|
||||
streamingMipmapsMemoryBudget: 512
|
||||
streamingMipmapsRenderersPerFrame: 512
|
||||
streamingMipmapsMaxLevelReduction: 2
|
||||
streamingMipmapsMaxFileIORequests: 1024
|
||||
particleRaycastBudget: 1024
|
||||
asyncUploadTimeSlice: 2
|
||||
asyncUploadBufferSize: 16
|
||||
asyncUploadPersistentBuffer: 1
|
||||
resolutionScalingFixedDPIFactor: 1
|
||||
customRenderPipeline: {fileID: 0}
|
||||
excludedTargetPlatforms: []
|
||||
- serializedVersion: 2
|
||||
name: Ultra
|
||||
pixelLightCount: 4
|
||||
shadows: 2
|
||||
shadowResolution: 2
|
||||
shadowProjection: 1
|
||||
shadowCascades: 4
|
||||
shadowDistance: 150
|
||||
shadowNearPlaneOffset: 3
|
||||
shadowCascade2Split: 0.33333334
|
||||
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||
shadowmaskMode: 1
|
||||
skinWeights: 255
|
||||
textureQuality: 0
|
||||
anisotropicTextures: 2
|
||||
antiAliasing: 2
|
||||
softParticles: 1
|
||||
softVegetation: 1
|
||||
realtimeReflectionProbes: 1
|
||||
billboardsFaceCameraPosition: 1
|
||||
vSyncCount: 1
|
||||
lodBias: 2
|
||||
maximumLODLevel: 0
|
||||
streamingMipmapsActive: 0
|
||||
streamingMipmapsAddAllCameras: 1
|
||||
streamingMipmapsMemoryBudget: 512
|
||||
streamingMipmapsRenderersPerFrame: 512
|
||||
streamingMipmapsMaxLevelReduction: 2
|
||||
streamingMipmapsMaxFileIORequests: 1024
|
||||
particleRaycastBudget: 4096
|
||||
asyncUploadTimeSlice: 2
|
||||
asyncUploadBufferSize: 16
|
||||
asyncUploadPersistentBuffer: 1
|
||||
resolutionScalingFixedDPIFactor: 1
|
||||
customRenderPipeline: {fileID: 0}
|
||||
excludedTargetPlatforms: []
|
||||
m_PerPlatformDefaultQuality:
|
||||
Android: 2
|
||||
Lumin: 5
|
||||
Nintendo Switch: 5
|
||||
PS4: 5
|
||||
Stadia: 5
|
||||
Standalone: 5
|
||||
WebGL: 3
|
||||
Windows Store Apps: 5
|
||||
XboxOne: 5
|
||||
iPhone: 2
|
||||
tvOS: 2
|
@ -0,0 +1,43 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!78 &1
|
||||
TagManager:
|
||||
serializedVersion: 2
|
||||
tags: []
|
||||
layers:
|
||||
- Default
|
||||
- TransparentFX
|
||||
- Ignore Raycast
|
||||
-
|
||||
- Water
|
||||
- UI
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
m_SortingLayers:
|
||||
- name: Default
|
||||
uniqueID: 0
|
||||
locked: 0
|
@ -0,0 +1,9 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!5 &1
|
||||
TimeManager:
|
||||
m_ObjectHideFlags: 0
|
||||
Fixed Timestep: 0.02
|
||||
Maximum Allowed Timestep: 0.33333334
|
||||
m_TimeScale: 1
|
||||
Maximum Particle Timestep: 0.03
|
@ -0,0 +1,35 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!310 &1
|
||||
UnityConnectSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 1
|
||||
m_Enabled: 0
|
||||
m_TestMode: 0
|
||||
m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
|
||||
m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
|
||||
m_ConfigUrl: https://config.uca.cloud.unity3d.com
|
||||
m_DashboardUrl: https://dashboard.unity3d.com
|
||||
m_TestInitMode: 0
|
||||
CrashReportingSettings:
|
||||
m_EventUrl: https://perf-events.cloud.unity3d.com
|
||||
m_Enabled: 0
|
||||
m_LogBufferSize: 10
|
||||
m_CaptureEditorExceptions: 1
|
||||
UnityPurchasingSettings:
|
||||
m_Enabled: 0
|
||||
m_TestMode: 0
|
||||
UnityAnalyticsSettings:
|
||||
m_Enabled: 0
|
||||
m_TestMode: 0
|
||||
m_InitializeOnStartup: 1
|
||||
UnityAdsSettings:
|
||||
m_Enabled: 0
|
||||
m_InitializeOnStartup: 1
|
||||
m_TestMode: 0
|
||||
m_IosGameId:
|
||||
m_AndroidGameId:
|
||||
m_GameIds: {}
|
||||
m_GameId:
|
||||
PerformanceReportingSettings:
|
||||
m_Enabled: 0
|
@ -0,0 +1,12 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!937362698 &1
|
||||
VFXManager:
|
||||
m_ObjectHideFlags: 0
|
||||
m_IndirectShader: {fileID: 0}
|
||||
m_CopyBufferShader: {fileID: 0}
|
||||
m_SortShader: {fileID: 0}
|
||||
m_StripUpdateShader: {fileID: 0}
|
||||
m_RenderPipeSettingsPath:
|
||||
m_FixedTimeStep: 0.016666668
|
||||
m_MaxDeltaTime: 0.05
|
@ -0,0 +1,8 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!890905787 &1
|
||||
VersionControlSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Mode: Visible Meta Files
|
||||
m_CollabEditorSettings:
|
||||
inProgressEnabled: 1
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"m_SettingKeys": [
|
||||
"VR Device Disabled",
|
||||
"VR Device User Alert"
|
||||
],
|
||||
"m_SettingValues": [
|
||||
"False",
|
||||
"False"
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!162 &1
|
||||
EditorUserSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 4
|
||||
m_ConfigSettings:
|
||||
RecentlyUsedScenePath-0:
|
||||
value: 22424703114646680e0b0227036c6c111b07142f1f2b233e2867083debf42d
|
||||
flags: 0
|
||||
vcSharedLogLevel:
|
||||
value: 0d5e400f0650
|
||||
flags: 0
|
||||
m_VCAutomaticAdd: 1
|
||||
m_VCDebugCom: 0
|
||||
m_VCDebugCmd: 0
|
||||
m_VCDebugOut: 0
|
||||
m_SemanticMergeMode: 2
|
||||
m_VCShowFailedCheckout: 1
|
||||
m_VCOverwriteFailedCheckoutAssets: 1
|
||||
m_VCProjectOverlayIcons: 1
|
||||
m_VCHierarchyOverlayIcons: 1
|
||||
m_VCOtherOverlayIcons: 1
|
||||
m_VCAllowAsyncUpdate: 1
|
12
tools/PostBuildUtility/PostBuildUtility.csproj
Normal file
12
tools/PostBuildUtility/PostBuildUtility.csproj
Normal file
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConsoleAppFramework" Version="3.3.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
62
tools/PostBuildUtility/Program.cs
Normal file
62
tools/PostBuildUtility/Program.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using ConsoleAppFramework;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PostBuildUtility
|
||||
{
|
||||
class Program : ConsoleAppBase
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
await Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<Program>(args);
|
||||
}
|
||||
|
||||
[Command("replace-to-unity")]
|
||||
public void ReplaceToUnity([Option(0)] string directory)
|
||||
{
|
||||
var replaceSet = new Dictionary<string, string>
|
||||
{
|
||||
// Remove nullable
|
||||
{"#nullable disable", "" },
|
||||
{"where T : notnull", "" },
|
||||
{"where TKey : notnull", "" },
|
||||
{">?", ">" }, // generics <T>?
|
||||
{"T?", "T" },
|
||||
{"T[]?", "T[]" },
|
||||
{"default!", "default" },
|
||||
{"null!", "null" },
|
||||
// project specified
|
||||
{"array!", "array" },
|
||||
{"Current!", "Current" },
|
||||
{"NotifyCollectionChangedEventHandler?", "NotifyCollectionChangedEventHandler" },
|
||||
{"PropertyChangedEventHandler?", "PropertyChangedEventHandler" },
|
||||
};
|
||||
|
||||
System.Console.WriteLine("Start to replace code, remove nullability.");
|
||||
var noBomUtf8 = new UTF8Encoding(false);
|
||||
|
||||
foreach (var path in Directory.EnumerateFiles(directory, "*.cs", SearchOption.AllDirectories))
|
||||
{
|
||||
var text = File.ReadAllText(path, Encoding.UTF8);
|
||||
var original = text;
|
||||
|
||||
foreach (var item in replaceSet)
|
||||
{
|
||||
text = text.Replace(item.Key, item.Value);
|
||||
}
|
||||
|
||||
if (text != original)
|
||||
{
|
||||
File.WriteAllText(path, text, noBomUtf8);
|
||||
}
|
||||
}
|
||||
|
||||
System.Console.WriteLine("Replace complete.");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user