diff --git a/lib/simulator/behaviors/InclusiveGatewayBehavior.js b/lib/simulator/behaviors/InclusiveGatewayBehavior.js index 159a3f6..d05bc96 100644 --- a/lib/simulator/behaviors/InclusiveGatewayBehavior.js +++ b/lib/simulator/behaviors/InclusiveGatewayBehavior.js @@ -14,21 +14,7 @@ export default function InclusiveGatewayBehavior( } InclusiveGatewayBehavior.prototype.enter = function(context) { - - // join right away if possible - if (this._canJoin(context)) { - this._join(context); - return; - } - - // join when all reachable tokens exit - // do not subscribe if a token of current parent scope is already waiting - const elementScopes = this._getElementScopes(context); - if (elementScopes.length > 1) { - return; - } - - this._joinWhenPossible(context); + this._tryJoin(context); }; InclusiveGatewayBehavior.prototype.exit = function(context) { @@ -69,44 +55,47 @@ InclusiveGatewayBehavior.prototype.exit = function(context) { }; -/** - * Returns true if there are either no remaining scopes in the parent scope, or if they are not - * reachable from the current element. - * - * @returns {Boolean} - */ -InclusiveGatewayBehavior.prototype._canJoin = function(context) { +InclusiveGatewayBehavior.prototype._tryJoin = function(context) { + const remainingScopes = this._getRemainingScopes(context); - // there are still some tokens to wait for - if (remainingScopes.length && this._canReachAnyScope(remainingScopes, context.element)) { - return false; + const remainingElements = remainingScopes.map(scope => scope.element); + + // join right away if possible + // this implies that there are no remaining scopes + // or non of the remaining scopes are reachable + if (!this._canReachAnyElement(remainingElements, context.element)) { + return this._join(context); } - return true; -}; + const elementScopes = this._getElementScopes(context); -InclusiveGatewayBehavior.prototype._joinWhenPossible = function(context) { const { scope } = context; - const remainingScopes = this._getRemainingScopes(context); + + // only subscribe to changes with the first + // element scope; prevent unneeded computation + if (elementScopes[0] !== scope) { + return; + } const event = this._simulator.waitForScopes(scope, remainingScopes); const subscription = this._simulator.subscribe(scope, event, () => { subscription.remove(); - if (this._canJoin(context)) { - this._join(context); - } else { - - // resubscribe to wait for remaining scopes - this._joinWhenPossible(context); - } + this._tryJoin(context); }); }; +/** + * Get scopes that may potentially be waited for, + * in the context of an inclusive gateway. + * + * @param {object} context + * @return {object[]} + */ InclusiveGatewayBehavior.prototype._getRemainingScopes = function(context) { const { scope, @@ -117,8 +106,9 @@ InclusiveGatewayBehavior.prototype._getRemainingScopes = function(context) { parent: parentScope } = scope; - return this._simulator.findScopes(scope => ( - scope.parent === parentScope && scope.element !== element)); + return this._simulator.findScopes( + scope => scope.parent === parentScope && scope.element !== element + ); }; InclusiveGatewayBehavior.prototype._join = function(context) { @@ -136,6 +126,13 @@ InclusiveGatewayBehavior.prototype._join = function(context) { this._simulator.exit(context); }; +/** + * Get scopes on the element for the given context. + * + * @param {object} context + * + * @return {object[]} scopes + */ InclusiveGatewayBehavior.prototype._getElementScopes = function(context) { const { element, @@ -148,26 +145,42 @@ InclusiveGatewayBehavior.prototype._getElementScopes = function(context) { }); }; -InclusiveGatewayBehavior.prototype._canReachAnyScope = function(scopes, currentElement, traversed = new Set()) { +/** + * Return true if any elements can be reached + * from the current element, searching the execution + * graph backwards. + * + * @param {object[]} elements + * @param {object} currentElement + * @param {Set} traversed + * + * @return {boolean} + */ +InclusiveGatewayBehavior.prototype._canReachAnyElement = function(elements, currentElement, traversed = new Set()) { + + if (!elements.length) { + return false; + } // avoid infinite recursion if (traversed.has(currentElement)) { return false; } + traversed.add(currentElement); - if (anyScopeIsOnElement(scopes, currentElement)) { + if (elements.some(e => e === currentElement)) { return true; } if (isSequenceFlow(currentElement)) { - return this._canReachAnyScope(scopes, currentElement.source, traversed); + return this._canReachAnyElement(elements, currentElement.source, traversed); } const incomingFlows = filterSequenceFlows(currentElement.incoming); for (const flow of incomingFlows) { - if (this._canReachAnyScope(scopes, flow, traversed)) { + if (this._canReachAnyElement(elements, flow, traversed)) { return true; } } @@ -178,8 +191,4 @@ InclusiveGatewayBehavior.prototype._canReachAnyScope = function(scopes, currentE InclusiveGatewayBehavior.$inject = [ 'simulator', 'activityBehavior' -]; - -function anyScopeIsOnElement(scopes, element) { - return scopes.some(scope => scope.element === element); -} +]; \ No newline at end of file