#nullable disable using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using JetBrains.Annotations; using JetBrains.Collections.Synchronized; using JetBrains.Collections.Viewable; using JetBrains.Diagnostics; using JetBrains.Lifetimes; using JetBrains.Rd.Base; using JetBrains.Rd.Util; using JetBrains.Serialization; // ReSharper disable InconsistentNaming namespace JetBrains.Rd.Impl { public class RdList : RdReactiveBase, IViewableList , INotifyCollectionChanged where V : notnull { private readonly ViewableList myList = new(new SynchronizedList()/*to have thread safe print*/); public RdList(CtxReadDelegate readValue, CtxWriteDelegate writeValue, long nextVersion = 1L) { myNextVersion = nextVersion; ValueCanBeNull = false; ReadValueDelegate = readValue; WriteValueDelegate = writeValue; //WPF integration this.AdviseAddRemove(Lifetime.Eternal, (kind, idx, v) => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(kind == AddRemove.Add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Remove, v, idx)); }); } //WPF integration public event NotifyCollectionChangedEventHandler CollectionChanged; public override event PropertyChangedEventHandler PropertyChanged; #region Serializers [PublicAPI] public CtxReadDelegate ReadValueDelegate { get; private set; } [PublicAPI] public CtxWriteDelegate WriteValueDelegate { get; private set; } [PublicAPI] public static RdList Read(SerializationCtx ctx, UnsafeReader reader) { return Read(ctx, reader, Polymorphic.Read, Polymorphic.Write); } [PublicAPI] public static RdList Read(SerializationCtx ctx, UnsafeReader reader,CtxReadDelegate readValue, CtxWriteDelegate writeValue) { var nextVersion = reader.ReadLong(); var id = reader.ReadRdId(); return new RdList(readValue, writeValue, nextVersion).WithId(id); } [PublicAPI] public static void Write(SerializationCtx ctx, UnsafeWriter writer, RdList value) { Assertion.Assert(!value.RdId.IsNil); writer.WriteInt64(value.myNextVersion); writer.Write(value.RdId); } #endregion #region Versions private const int versionedFlagShift = 2; //change when you change AddUpdateRemove private long myNextVersion; #endregion #region Init public bool OptimizeNested { [PublicAPI] get; set; } [ItemCanBeNull] private volatile SynchronizedList myBindDefinitions; protected override void Unbind() { base.Unbind(); myBindDefinitions = null; } protected override void PreInit(Lifetime lifetime, IProtocol proto) { base.PreInit(lifetime, proto); if (!OptimizeNested) { var definitions = new SynchronizedList(null, myList.Count); for (var index = 0; index < Count; index++) { var item = this[index]; if (item != null) { item.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); definitions.Add(TryPreBindValue(lifetime, item, index, false)); } } using var cookie = lifetime.UsingExecuteIfAlive(); if (cookie.Succeed) { Assertion.Assert(myBindDefinitions == null); myBindDefinitions = definitions; } else return; } proto.Wire.Advise(lifetime, this); } protected override void Init(Lifetime lifetime, IProtocol proto, SerializationCtx ctx) { base.Init(lifetime, proto, ctx); if (!OptimizeNested) { Change.Advise(lifetime, it => { if (IsLocalChange) { var definitions = TryGetBindDefinitions(lifetime); if (definitions == null) return; if (it.Kind != AddUpdateRemove.Add) definitions[it.Index]?.Terminate(); if (it.Kind == AddUpdateRemove.Remove) definitions.RemoveAt(it.Index); if (it.Kind != AddUpdateRemove.Remove && it.NewValue != null) { it.NewValue.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); definitions.Insert(it.Index, TryPreBindValue(lifetime, it.NewValue, it.Index, false)); } } }); } using (UsingLocalChange()) { Advise(lifetime, it => { if (!IsLocalChange) return; proto.Wire.Send(RdId, SendContext.Of(ctx, it, this), static(sendContext, stream) => { var sContext = sendContext.SzrCtx; var evt = sendContext.Event; var me = sendContext.This; stream.WriteInt64((me.myNextVersion++ << versionedFlagShift) | (long)evt.Kind); stream.WriteInt32(evt.Index); if (evt.Kind != AddUpdateRemove.Remove) me.WriteValueDelegate(sContext, stream, evt.NewValue); SendTrace?.Log($"list `{me.Location}` ({me.RdId}) :: {evt.Kind} :: index={evt.Index} :: " + $"version = {me.myNextVersion - 1}" + $"{(evt.Kind != AddUpdateRemove.Remove ? " :: value = " + evt.NewValue.PrintToString() : "")}"); }); if (!OptimizeNested) it.NewValue.BindPolymorphic(); }); } } protected override string ShortName => "list"; public override void OnWireReceived(IProtocol proto, SerializationCtx ctx, UnsafeReader stream, IRdWireableDispatchHelper dispatchHelper) { var header = stream.ReadLong(); var opType = header & ((1 << versionedFlagShift) - 1); var version = header >> versionedFlagShift; var index = stream.ReadInt(); var kind = (AddUpdateRemove) opType; var value = default(V); var isPut = kind is AddUpdateRemove.Add or AddUpdateRemove.Update; if (isPut) value = ReadValueDelegate(ctx, stream); var lifetime = dispatchHelper.Lifetime; var definition = value != null ? TryPreBindValue(lifetime, value, index, true) : null; dispatchHelper.Dispatch(() => { ReceiveTrace?.Log($"list `{Location}` ({RdId}) :: {kind} :: index={index} :: version = {version}{(isPut ? " :: value = " + value.PrintToString() : "")}"); if (version != myNextVersion) { definition?.Terminate(); Assertion.Fail("Version conflict for {0} Expected version {1} received {2}. Are you modifying a list from two sides?", Location, myNextVersion, version); } myNextVersion++; switch (kind) { case AddUpdateRemove.Add: { if (index < 0) { TryGetBindDefinitions(lifetime)?.Add(definition); myList.Add(value); } else { TryGetBindDefinitions(lifetime)?.Insert(index, definition); myList.Insert(index, value); } break; } case AddUpdateRemove.Update: { if (TryGetBindDefinitions(lifetime) is {} definitions) { definitions[index]?.Terminate(); definitions[index] = definition; } myList[index] = value; break; } case AddUpdateRemove.Remove: { if (TryGetBindDefinitions(lifetime) is {} definitions) { definitions[index]?.Terminate(); definitions.RemoveAt(index); } myList.RemoveAt(index); break; } default: throw new ArgumentOutOfRangeException(kind.ToString()); } }); } [CanBeNull] [ItemCanBeNull] private SynchronizedList TryGetBindDefinitions(Lifetime lifetime) { var definitions = myBindDefinitions; return lifetime.IsAlive ? definitions : null; } #nullable restore private LifetimeDefinition? TryPreBindValue(Lifetime lifetime, V? value, int index, bool bindAlso) { if (OptimizeNested || !value.IsBindable()) return null; var definition = new LifetimeDefinition { Id = value }; try { value.PreBindPolymorphic(definition.Lifetime, this, "["+index+"]"); //todo name will be not unique when you add elements in the middle of the list if (bindAlso) value.BindPolymorphic(); lifetime.Definition.Attach(definition, true); return definition; } catch { definition.Terminate(); throw; } } #nullable disable #endregion #region Read delegation IEnumerator IEnumerable.GetEnumerator() { return myList.GetEnumerator(); } public IEnumerator GetEnumerator() { return myList.GetEnumerator(); } public bool Contains(V item) { return myList.Contains(item); } public void CopyTo(V[] array, int arrayIndex) { myList.CopyTo(array, arrayIndex); } public int Count => myList.Count; public bool IsReadOnly => myList.IsReadOnly; public int IndexOf(V item) { return myList.IndexOf(item); } public ISource> Change => myList.Change; #endregion #region Write delegation public void Insert(int index, V value) { using (UsingLocalChange()) myList.Insert(index, value); } public void RemoveAt(int index) { using (UsingLocalChange()) myList.RemoveAt(index); } public V this[int index] { get => myList[index]; set { using (UsingLocalChange()) myList[index] = value; } } public bool Remove(V item) { using (UsingLocalChange()) { return myList.Remove(item); } } public void Add(V item) { using (UsingLocalChange()) { myList.Add(item); } } public void Clear() { using (UsingLocalChange()) { myList.Clear(); } } #endregion public void Advise(Lifetime lifetime, Action> handler) { if (IsBound) AssertThreading(); using (UsingDebugInfo()) myList.Advise(lifetime, handler); } public override RdBindableBase FindByRName(RName rName) { var rootName = rName.GetNonEmptyRoot(); var localName = rootName.LocalName.ToString(); if (!localName.StartsWith("[") || !localName.EndsWith("]")) return null; var stringIndex = localName.Substring(1, localName.Length - 2); if (!int.TryParse(stringIndex, out var index)) return null; if (!(myList.ElementAtOrDefault(index) is RdBindableBase element)) return null; if (rootName == rName) return element; return element.FindByRName(rName.DropNonEmptyRoot()); } public override void Print(PrettyPrinter printer) { base.Print(printer); if (!printer.PrintContent) return; printer.Print(" ["); if (Count > 0) printer.Println(); using (printer.IndentCookie()) { foreach (var v in this) { v.PrintEx(printer); printer.Println(); } } printer.Println("]"); } } }