internal ResourceHolder( TResourceObject resource, KeyWeakSharedSoleItemContainer <TMechanicalResourceKey, Unit> allComponentMechanicalsThisResource) { InterestedHoldingPartyToResource = new KeyWeakSharedSoleItemContainer <TInterestedHoldingParty, FSharpList <TResourceObject> >(ListModule.Singleton(resource)); AllComponentMechanicalsThisResource = allComponentMechanicalsThisResource; }
public ResourceHolder GetOrCreateResourceHolderFromMechanicalsSearch( Tuple <TMechanicalResourceKey, Func <KeyChainDownToValue> > firstMechanicalAndDown) { var allComponentMechanicalsTraversedSoFar_Accumulator = new KeyWeakSharedSoleItemContainer <TMechanicalResourceKey, Unit>(UnitValue); bool addToAccumulatorReturningTrueIfWentInNew(TMechanicalResourceKey mechanical) => allComponentMechanicalsTraversedSoFar_Accumulator.AddKeyIfNotAlreadyPresentWithRecreateIfMissing(mechanical, () => UnitValue).AddedWeakOwningKeyAsNew; // ** HERE ** is the rationale for everything below. We are building out, or traversing existing, WeakReference nodes. Key with the accumulator is no node is made for a mechanical already found // A combination of Rs can get us, say, mechanicals in the shape A A A B B A A C B C D C B A. Old version would build a list of WeakLinkNodes for all of those. // New version would make this into A B C D, as each one seen would never be repeated. This reduces the number of links that persist and the number that have to // be walked through, though ignoring them probably doesn't save that much time I guess. // So to consider - traversal-to-find and traversal-to-add follow the same algorithm here // Remember what WeakLinkNodes are ultimately for, to kill the ResourceHolder at the end of the chain if one goes away. If this is done right, there is still one node // per mechanical (rather than dupes), so the GC collection of one of them still cuts the chain. Trampoline <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> > > directlyBuildOutAbsent( KeyChainDownToValue next, Option <Tuple <WeakLinkNode, Action <Either <ResourceHolder, WeakLinkNode> > > > ifAnyBeforeThisIsFirstAndSinkForThis) => Trampoline.Build( next.Entry.Match( hereAndNext => { var mechanicalHere = hereAndNext.Item1; bool thisMechanicalWentInNew = addToAccumulatorReturningTrueIfWentInNew(mechanicalHere); if (thisMechanicalWentInNew) { var newLinkNodeHere = new WeakLinkNode(); var sinkNewToThis = Action((Either <ResourceHolder, WeakLinkNode> e) => newLinkNodeHere.KeyToTerminateOrNext.Add(mechanicalHere, e)); return (Either <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> >, Func <Trampoline <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> > > > > .FromRight( () => directlyBuildOutAbsent( hereAndNext.Item2(), //Some(Tuple.Create()) Some( // if there wasn't a first, there is now Tuple.Create( ifAnyBeforeThisIsFirstAndSinkForThis.Match( prior => { prior.Item2(Either <ResourceHolder, WeakLinkNode> .FromRight(newLinkNodeHere)); return prior.Item1; // perpetuate known 'first' }, () => // the one we created here is the first now. newLinkNodeHere), sinkNewToThis))))); } else { return // continue; but no new node to sink. (Either <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> >, Func <Trampoline <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> > > > > .FromRight( () => directlyBuildOutAbsent( hereAndNext.Item2(), // go to next ifAnyBeforeThisIsFirstAndSinkForThis // perpetuate any existing tree perception ))); } }, getObjectAndInterestedParties => // the very end - we return a value, and sink it to whatever node precedes { var objectAndInterestedParties = getObjectAndInterestedParties(); // return injector for value var theEndValue_OnlyUsedHereInImmediateSense = objectAndInterestedParties.Item1; // Put it in the ResourceHolder, where weakref behavior will track it var newHolder = new ResourceHolder(theEndValue_OnlyUsedHereInImmediateSense, allComponentMechanicalsTraversedSoFar_Accumulator); // (I think at time of writing) to notify any new subscriber as we transform off. AllResourceHoldersThisCache.AddUnrepresentedKeyWithRecreateIfMissing(newHolder, () => UnitValue); // Note - the closure on the right capturing theEndValue_... is using it immediately, the closure isn't itself captured long term by ResourceHolder's inner container; // But the value will go straight in if needed objectAndInterestedParties.Item2.ForEach(interestedHoldingParty => newHolder.AddKeyRebuildingHeldResourceFromPassedInOnlyIfItSomehowWasMissing(interestedHoldingParty, () => theEndValue_OnlyUsedHereInImmediateSense)); var e = Either <ResourceHolder, WeakLinkNode> .FromLeft(newHolder); return // return trampoline final result (Either <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> >, Func <Trampoline <InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> > > > > .FromLeft( ifAnyBeforeThisIsFirstAndSinkForThis.Match( veryFirstAndSinkForThis => // there was a node before this we can sink to { veryFirstAndSinkForThis.Item2(e); return new InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> >( toInject: Either <ResourceHolder, WeakLinkNode> .FromRight(veryFirstAndSinkForThis.Item1), // caller needs to inject this, the very first toReturn: e); }, () => // there was no prior WeakNode created - just needed this result node - to both inject, and return as value // this could be a bit weird - not sure how you could get in to directlyBuildOutAbsent and not either build a weaknode, or have one passed in new InjectAndOuterReturn <Either <ResourceHolder, WeakLinkNode> >( toInject: e, // caller needs to inject this, the result holder node only. toReturn: e)))); })); Trampoline <ResourceHolder> discovered( WeakLinkNode currentNode, Tuple <TMechanicalResourceKey, Func <KeyChainDownToValue> > currentMechanicalAndDown) { var mechanicalHere = currentMechanicalAndDown.Item1; bool thisMechanicalIsNewToOurTraversal = addToAccumulatorReturningTrueIfWentInNew(mechanicalHere); return (Trampoline.Build( thisMechanicalIsNewToOurTraversal ? currentNode.KeyToTerminateOrNext.GetAndIfAbsentBuildSplitInjectAndOuterReturnInsideTheLock( mechanicalHere, () => // short-circuiting Trampoline.RunToEnd( directlyBuildOutAbsent ( currentMechanicalAndDown.Item2(), None <Tuple <WeakLinkNode, Action <Either <ResourceHolder, WeakLinkNode> > > >())) // sending 'next' in for sub's use, the inject returned will be sent to currentMechanical.Value by virtue of being in inject predicate ).Map( x => x, // found an existing, which already contains what's needed node => // found a matching node - proceed with next stage of discovery Func( () => currentMechanicalAndDown.Item2().Entry.Match( // dig out the next in the path t => discovered(node, t), // another mechanical - search around again end => // we're at a value-point, throw new Exception("Contradiction from source - found a full node from prior mechanical; but source claims it's a value. That is, a prior mechanical leading to another mechanical/junction now represents termination point") //discovered(node, currentMechanical.Next) ))) : Either <ResourceHolder, Func <Trampoline <ResourceHolder> > > .FromRight( () => Trampoline.Build( Either <ResourceHolder, Func <Trampoline <ResourceHolder> > > .FromRight( () => currentMechanicalAndDown.Item2().Entry.Match( t => discovered(currentNode, t), end => throw new Exception("Contradiction from source - found a full node from prior mechanical; but source claims it's a value. That is, a prior mechanical leading to another mechanical/junction now represents termination point"))))))); } // Theory on Exclusivity here is sound - anyone attempting discovery could cause resource calculation, any +1 // getting past here could end up paying twice. // Locking just at the root is fine, as the WeakNodes never get out of this class into others. return(With(mExclusivity, () => Trampoline.RunToEnd(discovered(MechanicalsDownToResource, firstMechanicalAndDown)))); }