internal void MergeReadStructsIntoPendingReads(IDictionary <int, List <AbstractStruct> > clientStructsRefs) { var pendingClientStructRefs = _pendingClientStructRefs; foreach (var kvp in clientStructsRefs) { var client = kvp.Key; var structRefs = kvp.Value; if (!pendingClientStructRefs.TryGetValue(client, out var pendingStructRefs)) { pendingClientStructRefs[client] = new PendingClientStructRef { Refs = structRefs }; } else { // Merge into existing structRefs. if (pendingStructRefs.NextReadOperation > 0) { pendingStructRefs.Refs.RemoveRange(0, pendingStructRefs.NextReadOperation); } var merged = pendingStructRefs.Refs; for (int i = 0; i < structRefs.Count; i++) { merged.Add(structRefs[i]); } merged.Sort((a, b) => a.Id.Clock - b.Id.Clock); pendingStructRefs.NextReadOperation = 0; pendingStructRefs.Refs = merged; } } }
/// <summary> /// Resume computing structs generated by struct readers. /// <br/> /// While there is something to do, we integrate structs in this order: /// 1. Top element on stack, if stack is not empty. /// 2. Next element from current struct reader (if empty, use next struct reader). /// <br/> /// If struct causally depends on another struct (ref.missing), we put next reader of /// 'ref.id.client' on top of stack. /// <br/> /// At some point we find a struct that has no causal dependencies, then we start /// emptying the stack. /// <br/> /// It is not possible to have circles: i.e. struct1 (from client1) depends on struct2 (from client2) /// depends on struct3 (from client1). Therefore, the max stack size is equal to 'structReaders.length'. /// <br/> /// This method is implemented in a way so that we can resume computation if this update causally /// depends on another update. /// </summary> internal void ResumeStructIntegration(Transaction transaction) { // @todo: Don't forget to append stackhead at the end. var stack = _pendingStack; var clientsStructRefs = _pendingClientStructRefs; if (clientsStructRefs.Count == 0) { return; } // Sort them so taht we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user. var clientsStructRefsIds = clientsStructRefs.Keys.ToList(); clientsStructRefsIds.Sort(); PendingClientStructRef getNextStructTarget() { var nextStructsTarget = clientsStructRefs[clientsStructRefsIds[clientsStructRefsIds.Count - 1]]; while (nextStructsTarget.Refs.Count == nextStructsTarget.NextReadOperation) { clientsStructRefsIds.RemoveAt(clientsStructRefsIds.Count - 1); if (clientsStructRefsIds.Count > 0) { nextStructsTarget = clientsStructRefs[clientsStructRefsIds[clientsStructRefsIds.Count - 1]]; } else { _pendingClientStructRefs.Clear(); return(null); } } return(nextStructsTarget); } var curStructsTarget = getNextStructTarget(); if (curStructsTarget == null && stack.Count == 0) { return; } var stackHead = stack.Count > 0 ? stack.Pop() : curStructsTarget.Refs[curStructsTarget.NextReadOperation++]; // Caching the state because it is used very often. var state = new Dictionary <int, int>(); // Iterate over all struct readers until we are done. while (true) { if (!state.TryGetValue(stackHead.Id.Client, out int localClock)) { localClock = GetState(stackHead.Id.Client); state[stackHead.Id.Client] = localClock; } var offset = stackHead.Id.Clock < localClock ? localClock - stackHead.Id.Clock : 0; if (stackHead.Id.Clock + offset != localClock) { // A previous message from this client is missing. // Check if there is a pending structRef with a smaller clock and switch them. if (!clientsStructRefs.TryGetValue(stackHead.Id.Client, out var structRefs)) { structRefs = new PendingClientStructRef(); } if (structRefs.Refs.Count != structRefs.NextReadOperation) { var r = structRefs.Refs[structRefs.NextReadOperation]; if (r.Id.Clock < stackHead.Id.Clock) { // Put ref with smaller clock on stack instead and continue. structRefs.Refs[structRefs.NextReadOperation] = stackHead; stackHead = r; // Sort the set because this approach might bring the list out of order. structRefs.Refs.RemoveRange(0, structRefs.NextReadOperation); structRefs.Refs.Sort((a, b) => a.Id.Clock - b.Id.Clock); structRefs.NextReadOperation = 0; continue; } } // Wait until missing struct is available. stack.Push(stackHead); return; } var missing = stackHead.GetMissing(transaction, this); if (missing == null) { if (offset == 0 || offset < stackHead.Length) { stackHead.Integrate(transaction, offset); state[stackHead.Id.Client] = stackHead.Id.Clock + stackHead.Length; } // Iterate to next stackHead. if (stack.Count > 0) { stackHead = stack.Pop(); } else if (curStructsTarget != null && curStructsTarget.NextReadOperation < curStructsTarget.Refs.Count) { stackHead = curStructsTarget.Refs[curStructsTarget.NextReadOperation++]; } else { curStructsTarget = getNextStructTarget(); if (curStructsTarget == null) { // We are done! break; } else { stackHead = curStructsTarget.Refs[curStructsTarget.NextReadOperation++]; } } } else { // Get the struct reader that has the missing struct. if (!clientsStructRefs.TryGetValue(missing.Value, out var structRefs)) { structRefs = new PendingClientStructRef(); } if (structRefs.Refs.Count == structRefs.NextReadOperation) { // This update message causally depends on another update message. stack.Push(stackHead); return; } stack.Push(stackHead); stackHead = structRefs.Refs[structRefs.NextReadOperation++]; } } _pendingClientStructRefs.Clear(); }