%QueueFinishedDispatcher Calls callback functions when tasks finish
%   QueueFinishedDispatcher contains a map between taskIDs and callback
%   functions for that task. It can listen on FevalQueue's FutureCompleted
%   event, and call the callback function for the completed task.

% Copyright 2016-2021 The MathWorks, Inc.

classdef QueueFinishedDispatcher < handle
   
    properties (Access = private)
        DispatchMap;
    end
    
    methods
        function obj = QueueFinishedDispatcher()
            % Map containing the callbacks. The keys are the future Ids.
            % Since multiple futures can share the same ID (if copies are
            % created when accessing the pool.FevalQueue), the values are
            % cell arrays looking like: {{UID1, funcHandle1}, {UID2,
            % funcHandle2}, ... } with the unique IDs for each future.
            % Copies are uncommon, so there will typically only be one
            % uid/function handle pair for each ID
            obj.DispatchMap = containers.Map('KeyType', 'int32', 'ValueType', 'any');
        end

        function addFuncToDispatch(obj, ID, UID, funcHandle)
            if obj.DispatchMap.isKey(ID)
                % This is another future with the same id, add the
                % UID/funcHandle pair to the end of the cell array.
                funcs = obj.DispatchMap(ID);
                funcs{end+1} = {UID, funcHandle};
                obj.DispatchMap(ID) = funcs;
            else
                obj.DispatchMap(ID) = {{UID,funcHandle}};
            end
        end
        
        function removeFuncToDispatch(obj, ID, UID)
            if isvalid(obj.DispatchMap)
                if obj.DispatchMap.isKey(ID)
                    funcs = obj.DispatchMap(ID);
                    obj.DispatchMap.remove(ID);
                    
                    indicesToRemove = [];
                    for i = 1:numel(funcs)
                        if funcs{i}{1} == UID
                            indicesToRemove = [indicesToRemove, i]; %#ok<AGROW>
                        end
                    end
                    
                    if ~isempty(indicesToRemove)
                        funcs(indicesToRemove) = [];
                    end
                    
                    % If we deleted the last UID/funcHandle pair, we don't
                    % add it back to the map.
                    if ~isempty(funcs)
                        obj.DispatchMap(ID) = funcs;
                    end
                end
            else
                assert(false, 'removeFuncToDispatch called after destruction.');
            end
        end
        
        function dispatch(obj, id)
             if ~obj.DispatchMap.isKey(id)
                 %No function in the map for this task, do nothing.
                 return
             end

             % Look up function, execute it, and remove it from the map
             funcs = obj.DispatchMap(id);
             remove(obj.DispatchMap, id);
             for i = 1:numel(funcs)
                 func = funcs{i}{2};
                 feval(func, id);
             end
        end
    end
end
