Skip to content

Instantly share code, notes, and snippets.

@0xF6
Created September 15, 2023 17:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xF6/31d2cb03333c9a5e22de8d2a9415ef8d to your computer and use it in GitHub Desktop.
Save 0xF6/31d2cb03333c9a5e22de8d2a9415ef8d to your computer and use it in GitHub Desktop.
internal interface IObserverLinkedList<T>
{
void UnsubscribeNode(ObserverNode<T> node);
}
internal sealed class ObserverNode<T> : IObserver<T>, IDisposable
{
private readonly IObserver<T> observer;
private IObserverLinkedList<T> list;
public ObserverNode<T> Previous { get; internal set; }
public ObserverNode<T> Next { get; internal set; }
public ObserverNode(IObserverLinkedList<T> list, IObserver<T> observer) =>
(this.list, this.observer) = (list, observer);
public void OnNext(T value) => this.observer.OnNext(value);
public void OnError(Exception error) => this.observer.OnError(error);
public void OnCompleted() => this.observer.OnCompleted();
public void Dispose() => Interlocked.Exchange(ref this.list, null)?.UnsubscribeNode(this);
}
public interface ISerializedPropertyAccessor
{
bool HasDefaultValue { get; }
}
public interface ISerializedPropertyAccessor<in T> : ISerializedPropertyAccessor
{
void SetValue(T value);
}
public class SerializedProperty<T> : IReactiveProperty<T>,
IReadOnlyReactiveProperty<T>,
IObservable<T>,
IDisposable,
IOptimizedObservable<T>,
IObserverLinkedList<T>,
ISerializedPropertyAccessor<T>
{
private T value;
private ObserverNode<T> root;
private ObserverNode<T> last;
private bool isDisposed;
public T Value
{
get => this.value;
set
{
if (this.GetEqualityComparer().Equals(this.value, value))
return;
this.SetValue(value);
if (this.isDisposed)
return;
this.RaiseOnNext(ref value);
}
}
public bool HasValue => true;
public bool HasDefaultValue { get; private set; } = true;
public SerializedProperty()
: this(default(T)) =>
this.shadowProperty = new ShadowProperty<T>();
public SerializedProperty(T initialValue) =>
this.SetValue(initialValue, true);
private void RaiseOnNext(ref T v)
{
for (ObserverNode<T> observerNode = this.root; observerNode != null; observerNode = observerNode.Next)
observerNode.OnNext(v);
}
protected void SetValue(T v, bool isInitial = false)
{
if (!isInitial) HasDefaultValue = false;
this.value = v;
}
public void SetValueAndForceNotify(T v)
{
this.SetValue(v);
if (this.isDisposed)
return;
this.RaiseOnNext(ref v);
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (this.isDisposed)
{
observer.OnCompleted();
return Disposable.Empty;
}
observer.OnNext(this.value);
ObserverNode<T> observerNode = new ObserverNode<T>(this, observer);
if (this.root == null)
this.root = this.last = observerNode;
else
{
this.last.Next = observerNode;
observerNode.Previous = this.last;
this.last = observerNode;
}
return observerNode;
}
void IObserverLinkedList<T>.UnsubscribeNode(ObserverNode<T> node)
{
if (node == this.root)
this.root = node.Next;
if (node == this.last)
this.last = node.Previous;
if (node.Previous != null)
node.Previous.Next = node.Next;
if (node.Next == null)
return;
node.Next.Previous = node.Previous;
}
public void Dispose()
{
shadowProperty.Dispose();
this.Dispose(true);
GC.SuppressFinalize((object)this);
}
protected virtual void Dispose(bool disposing)
{
if (this.isDisposed)
return;
ObserverNode<T> observerNode = this.root;
this.root = this.last = (ObserverNode<T>)null;
this.isDisposed = true;
for (; observerNode != null; observerNode = observerNode.Next)
observerNode.OnCompleted();
}
void ISerializedPropertyAccessor<T>.SetValue(T v) => SetValue(v, true);
public override string ToString() => (object)this.value != null ? this.value.ToString() : "(null)";
public bool IsRequiredSubscribeOnCurrentThread() => false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment